Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d4f8ca5d5 | ||
|
|
8083d3f2d2 | ||
|
|
136463f074 | ||
|
|
a52d66cd88 | ||
|
|
7ff9743acc | ||
|
|
1825ea5581 | ||
|
|
4260125040 | ||
|
|
867f2ac40c | ||
|
|
4c54be1758 | ||
|
|
63bc74eca9 | ||
|
|
11486c0647 | ||
|
|
90ad1b86db | ||
|
|
f6ffb58794 | ||
|
|
1684c62032 | ||
|
|
72c3cb8d88 | ||
|
|
9d06bbe9af | ||
|
|
d36b0c231f | ||
|
|
3eab69034c | ||
|
|
42ff4baac0 | ||
|
|
27bde0ee5b | ||
|
|
e9da4c1d78 | ||
|
|
2e8a3663a5 | ||
|
|
9b1aa1288d | ||
|
|
3e0b7bafc3 | ||
|
|
0f8b9711ed | ||
|
|
5d2b01e6f3 | ||
|
|
c3564df658 | ||
|
|
2b800544bc | ||
|
|
4cf21df78e | ||
|
|
9b15adbf7b | ||
|
|
3f950c1731 | ||
|
|
144373e3b1 | ||
|
|
cceb69b387 | ||
|
|
dd5df7f54f | ||
|
|
78afa0cf26 | ||
|
|
6dd40c3b24 | ||
|
|
bb1bd9becb | ||
|
|
48b0765983 |
@@ -0,0 +1 @@
|
|||||||
|
.git
|
||||||
@@ -1 +1,3 @@
|
|||||||
/.idea/*
|
/.idea/*
|
||||||
|
/.superpowers/
|
||||||
|
/docs/superpowers/
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
# 阶段1: 构建
|
||||||
|
FROM golang:alpine AS builder
|
||||||
|
|
||||||
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
|
||||||
|
apk add --no-cache git ca-certificates tzdata
|
||||||
|
|
||||||
|
ENV TZ=Asia/Shanghai
|
||||||
|
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
|
||||||
|
ENV GO111MODULE=on
|
||||||
|
ENV GOPROXY=https://goproxy.cn,direct
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
|
ENV GOTOOLCHAIN=auto
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN go mod download && go mod tidy
|
||||||
|
|
||||||
|
RUN go build -ldflags="-s -w" -o main ./main.go
|
||||||
|
|
||||||
|
|
||||||
|
EXPOSE 3007
|
||||||
|
|
||||||
|
CMD ["./main"]
|
||||||
+30
-9
@@ -9,10 +9,10 @@ server:
|
|||||||
database:
|
database:
|
||||||
default:
|
default:
|
||||||
- type: "pgsql"
|
- type: "pgsql"
|
||||||
host: "116.204.74.41"
|
host: "192.168.0.83"
|
||||||
port: "15432"
|
port: "15432"
|
||||||
user: "postgres"
|
user: "sql9f15b63fd203b36e"
|
||||||
pass: "Bjang09@686^*^"
|
pass: "1ec94b1acdaf57b66030242d418fad5a"
|
||||||
name: "shop_user_trade"
|
name: "shop_user_trade"
|
||||||
role: "master"
|
role: "master"
|
||||||
debug: false
|
debug: false
|
||||||
@@ -28,10 +28,10 @@ database:
|
|||||||
deletedAt: "deleted_at"
|
deletedAt: "deleted_at"
|
||||||
timeMaintainDisabled: false
|
timeMaintainDisabled: false
|
||||||
- type: "pgsql"
|
- type: "pgsql"
|
||||||
host: "116.204.74.41"
|
host: "192.168.0.83"
|
||||||
port: "15432"
|
port: "15432"
|
||||||
user: "postgres"
|
user: "sql9f15b63fd203b36e"
|
||||||
pass: "Bjang09@686^*^"
|
pass: "1ec94b1acdaf57b66030242d418fad5a"
|
||||||
name: "shop_user_trade"
|
name: "shop_user_trade"
|
||||||
role: "slave"
|
role: "slave"
|
||||||
debug: false
|
debug: false
|
||||||
@@ -46,10 +46,31 @@ database:
|
|||||||
updatedAt: "updated_at"
|
updatedAt: "updated_at"
|
||||||
deletedAt: "deleted_at"
|
deletedAt: "deleted_at"
|
||||||
timeMaintainDisabled: false
|
timeMaintainDisabled: false
|
||||||
|
wallet:
|
||||||
|
- type: "pgsql"
|
||||||
|
host: "192.168.0.83"
|
||||||
|
port: "15432"
|
||||||
|
user: "sql9f15b63fd203b36e"
|
||||||
|
pass: "1ec94b1acdaf57b66030242d418fad5a"
|
||||||
|
name: "wallet"
|
||||||
|
prefix: "wallet_" # (可选)表名前缀
|
||||||
|
role: "master"
|
||||||
|
debug: true # (可选)开启调试模式
|
||||||
|
dryRun: false # (可选)ORM空跑(只读不写)
|
||||||
|
charset: "utf8" # (可选)数据库编码(如: utf8mb4/utf8/gbk/gb2312),一般设置为utf8mb4。默认为utf8。
|
||||||
|
timezone: "Asia/Shanghai" # (可选)时区配置,例如:Local
|
||||||
|
maxIdle: 5 # (可选)连接池最大闲置的连接数(默认10)
|
||||||
|
maxOpen: 20 # (可选)连接池最大打开的连接数(默认无限制)
|
||||||
|
maxLifetime: "30s" # (可选)连接对象可重复使用的时间长度(默认30秒)
|
||||||
|
maxIdleConnTime: "30s" # (可选,v2.10新增)连接池中空闲连接的最大生存时间(默认30秒)。可以通过配置文件或SetConnMaxIdleTime方法设置,避免长时间空闲连接占用资源。
|
||||||
|
createdAt: "created_at" # (可选)自动创建时间字段名称
|
||||||
|
updatedAt: "updated_at" # (可选)自动更新时间字段名称
|
||||||
|
deletedAt: "deleted_at" # (可选)软删除时间字段名称
|
||||||
|
timeMaintainDisabled: false # (可选)是否完全关闭时间更新特性,为true时CreatedAt/UpdatedAt/DeletedAt都将失效
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
default:
|
default:
|
||||||
address: 116.204.74.41:6379
|
address: 192.168.0.83:6379
|
||||||
db: 0
|
db: 0
|
||||||
idleTimeout: "60s"
|
idleTimeout: "60s"
|
||||||
maxConnLifetime: "90s"
|
maxConnLifetime: "90s"
|
||||||
@@ -60,7 +81,7 @@ redis:
|
|||||||
maxActive: 100
|
maxActive: 100
|
||||||
|
|
||||||
consul:
|
consul:
|
||||||
address: 116.204.74.41:8500
|
address: 192.168.0.83:8500
|
||||||
|
|
||||||
jaeger:
|
jaeger:
|
||||||
addr: 116.204.74.41:4318
|
addr: 192.168.0.83:4318
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package pricing
|
||||||
|
|
||||||
|
// ChargeMode 计费方式(全 DB 配置,代码不写死金额;新增方式在 charge_calc 计算器注册表加一项)
|
||||||
|
type ChargeMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChargeModePerItem ChargeMode = "per_item" // 按条:档位开放、上不封顶(rules.tiers + overflow)
|
||||||
|
ChargeModePerSecond ChargeMode = "per_second" // 按秒(workflow:整秒向上取整;model:精确秒)
|
||||||
|
ChargeModePerToken ChargeMode = "per_token" // 按token(workflow 结算时按各模型 subject 实时价)
|
||||||
|
ChargeModePer1K ChargeMode = "per_1K" // 每千token(模型 token 计费)
|
||||||
|
ChargeModePer1M ChargeMode = "per_1M" // 每百万token(模型 token 计费)
|
||||||
|
ChargeModePer1 ChargeMode = "per_1" // 每个/每张图(图片按张数)
|
||||||
|
ChargeModePerMinute ChargeMode = "per_minute" // 每分钟(音频时长)
|
||||||
|
ChargeModePerHour ChargeMode = "per_hour" // 每小时(音频时长)
|
||||||
|
ChargeModePerChar ChargeMode = "per_char" // 每字(音频按字数)
|
||||||
|
ChargeModePerPeriod ChargeMode = "per_period" // 周期订阅(business 按年)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ModelUnitSet model subject 允许的计费单位(spec §4.2)
|
||||||
|
var ModelUnitSet = map[ChargeMode]bool{
|
||||||
|
ChargeModePer1K: true, ChargeModePer1M: true, ChargeModePer1: true,
|
||||||
|
ChargeModePerSecond: true, ChargeModePerMinute: true, ChargeModePerHour: true, ChargeModePerChar: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelMediaSet 模型费率 mediaPrices 允许的媒体类型键(输入媒体白名单,前端下拉同源)
|
||||||
|
var ModelMediaSet = map[string]struct{}{
|
||||||
|
"text": {}, "audio": {}, "video": {}, "image": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeriodSet 周期允许集合(per_period,扩月/季只加值)
|
||||||
|
var PeriodSet = map[string]bool{"year": true}
|
||||||
|
|
||||||
|
// ChargeModeNames 计费方式中文名(前端下拉渲染;全枚举覆盖,未命中回退 mode 本身)
|
||||||
|
var ChargeModeNames = map[ChargeMode]string{
|
||||||
|
ChargeModePerItem: "按条",
|
||||||
|
ChargeModePerSecond: "按秒",
|
||||||
|
ChargeModePerToken: "按token",
|
||||||
|
ChargeModePer1K: "每千token",
|
||||||
|
ChargeModePer1M: "每百万token",
|
||||||
|
ChargeModePer1: "每张/每个",
|
||||||
|
ChargeModePerMinute: "每分钟",
|
||||||
|
ChargeModePerHour: "每小时",
|
||||||
|
ChargeModePerChar: "每字",
|
||||||
|
ChargeModePerPeriod: "周期订阅",
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package pricing
|
||||||
|
|
||||||
|
// ChargeOrderStatus 计费单状态(状态机单迁移:CREATED → SETTLED / FAILED)
|
||||||
|
type ChargeOrderStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChargeOrderStatusCreated ChargeOrderStatus = 1 // 已建单(未结算,不动钱)
|
||||||
|
ChargeOrderStatusSettled ChargeOrderStatus = 2 // 已结算(实收完成)
|
||||||
|
ChargeOrderStatusFailed ChargeOrderStatus = 3 // 已失败(不扣费)
|
||||||
|
)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package pricing
|
||||||
|
|
||||||
|
// SubjectType 计价对象类型(固定枚举 = pricing_config 唯一键第一部分)
|
||||||
|
type SubjectType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SubjectTypeWorkflow SubjectType = "workflow" // 工作流(固定单主体)
|
||||||
|
SubjectTypeModel SubjectType = "model" // 模型(model-gateway 系统模型,subject_id=模型ID)
|
||||||
|
SubjectTypeBusiness SubjectType = "business" // 业务模块(固定写死列表)
|
||||||
|
)
|
||||||
|
|
||||||
|
// WorkflowSubjectID 工作流固定 subject_id
|
||||||
|
const WorkflowSubjectID = "workflow"
|
||||||
|
|
||||||
|
// BusinessSubject 业务模块枚举项(固定写死,新增业务模块改这里)
|
||||||
|
type BusinessSubject struct {
|
||||||
|
SubjectID string `json:"subjectId"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ChargeModes []ChargeMode `json:"chargeModes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BusinessSubjects 业务模块固定列表(spec §7:与「固定枚举」一致)
|
||||||
|
var BusinessSubjects = []BusinessSubject{
|
||||||
|
{SubjectID: "ai_customer_service", Name: "AI客服", ChargeModes: []ChargeMode{ChargeModePerPeriod}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkflowChargeModes workflow 主体可选计费方式(与 charge_calc.go workflowCalculators 三键一致)
|
||||||
|
var WorkflowChargeModes = []ChargeMode{ChargeModePerItem, ChargeModePerSecond, ChargeModePerToken}
|
||||||
|
|
||||||
|
// ModelTypeCode 模型类型编码(透传 model-gateway 枚举,管理端按此分组渲染编辑器)
|
||||||
|
type ModelTypeCode = int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ModelTypeReasoning ModelTypeCode = 100 // 推理
|
||||||
|
ModelTypeImage ModelTypeCode = 200 // 图片
|
||||||
|
ModelTypeAudio ModelTypeCode = 300 // 音频
|
||||||
|
ModelTypeVector ModelTypeCode = 400 // 向量
|
||||||
|
ModelTypeOmni ModelTypeCode = 500 // 多模态
|
||||||
|
ModelTypeVideo ModelTypeCode = 600 // 视频
|
||||||
|
ModelTypeCode2 ModelTypeCode = 700 // 代码
|
||||||
|
)
|
||||||
@@ -1,11 +1,20 @@
|
|||||||
package public
|
package public
|
||||||
|
|
||||||
|
// 数据库名称
|
||||||
|
const (
|
||||||
|
DbNameWallet = "wallet"
|
||||||
|
)
|
||||||
|
|
||||||
// 数据库表名
|
// 数据库表名
|
||||||
const (
|
const (
|
||||||
TableNameKnapsack = "knapsack" // 背包表
|
TableNameKnapsack = "knapsack" // 背包表
|
||||||
TableNameKnapsackLog = "knapsack_log" // 背包日志表
|
TableNameKnapsackLog = "knapsack_log" // 背包日志表
|
||||||
TableNameMarket = "market" // 市场表
|
TableNameMarket = "market" // 市场表
|
||||||
TableNameMarketLog = "market_log" // 市场日志表
|
TableNameMarketLog = "market_log" // 市场日志表
|
||||||
TableNameWallet = "wallet" // 钱包表
|
|
||||||
TableNameWalletLog = "wallet_log" // 钱包日志表
|
TableNameWalletAccount = "account" // 钱包表
|
||||||
|
TableNameWalletAccountLog = "account_log" // 钱包日志表
|
||||||
|
|
||||||
|
TableNamePricingConfig = "pricing_config" // 计价配置表
|
||||||
|
TableNameChargeOrder = "charge_order" // 计费单表
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package wallet
|
package wallet
|
||||||
|
|
||||||
// WalletLogType 钱包日志操作类型
|
// WalletLogType 钱包账务类型
|
||||||
|
// 语义按金额变动定义(与钱包 status 的整钱包冻结无关):
|
||||||
|
//
|
||||||
|
// income 收入/充值 balance += amount
|
||||||
|
// expense 支出(结算实扣) balance -= amount(允许为负=欠费)
|
||||||
type WalletLogType string
|
type WalletLogType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
WalletLogTypeIncome WalletLogType = "income" // 收入
|
WalletLogTypeIncome WalletLogType = "income" // 收入/充值
|
||||||
WalletLogTypeExpense WalletLogType = "expense" // 支出
|
WalletLogTypeExpense WalletLogType = "expense" // 支出(结算实扣,允许负余额)
|
||||||
WalletLogTypeFreeze WalletLogType = "freeze" // 冻结
|
|
||||||
WalletLogTypeUnfreeze WalletLogType = "unfreeze" // 解冻
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
pricingDto "shop-user-trade/model/dto/pricing"
|
||||||
|
pricingService "shop-user-trade/service/pricing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Pricing = new(pricingController)
|
||||||
|
|
||||||
|
type pricingController struct{}
|
||||||
|
|
||||||
|
// OpenOrder 建单(不动钱)
|
||||||
|
func (c *pricingController) OpenOrder(ctx context.Context, req *pricingDto.OpenOrderReq) (*pricingDto.ChargeOrderInfo, error) {
|
||||||
|
return pricingService.Pricing.OpenOrder(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settle 结算(按最终产出)
|
||||||
|
func (c *pricingController) Settle(ctx context.Context, req *pricingDto.SettleReq) (*pricingDto.ChargeOrderInfo, error) {
|
||||||
|
return pricingService.Pricing.Settle(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel 取消(按已消耗用量结算)
|
||||||
|
func (c *pricingController) Cancel(ctx context.Context, req *pricingDto.CancelReq) (*pricingDto.ChargeOrderInfo, error) {
|
||||||
|
return pricingService.Pricing.Cancel(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail 失败处理(不扣费)
|
||||||
|
func (c *pricingController) Fail(ctx context.Context, req *pricingDto.FailReq) (*pricingDto.ChargeOrderInfo, error) {
|
||||||
|
return pricingService.Pricing.Fail(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrder 查询计费单
|
||||||
|
func (c *pricingController) GetOrder(ctx context.Context, req *pricingDto.GetOrderInfoReq) (*pricingDto.ChargeOrderInfo, error) {
|
||||||
|
return pricingService.Pricing.GetOrderInfo(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalcFee 按用量计算费用(不建单不扣费)
|
||||||
|
func (c *pricingController) CalcFee(ctx context.Context, req *pricingDto.CalcFeeReq) (*pricingDto.CalcFeeRes, error) {
|
||||||
|
return pricingService.Pricing.CalcFee(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConfig 保存计价配置
|
||||||
|
func (c *pricingController) SaveConfig(ctx context.Context, req *pricingDto.SavePricingConfigReq) (*pricingDto.SavePricingConfigRes, error) {
|
||||||
|
return pricingService.Pricing.SavePricingConfig(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig 查询计价配置
|
||||||
|
func (c *pricingController) GetConfig(ctx context.Context, req *pricingDto.GetPricingConfigReq) (*pricingDto.GetPricingConfigRes, error) {
|
||||||
|
return pricingService.Pricing.GetPricingConfig(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subjects 计价对象枚举
|
||||||
|
func (c *pricingController) Subjects(ctx context.Context, req *pricingDto.SubjectListReq) (*pricingDto.SubjectListRes, error) {
|
||||||
|
return pricingService.Pricing.ListSubjects(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChargeModes 查询主体可选计费方式
|
||||||
|
func (c *pricingController) ChargeModes(ctx context.Context, req *pricingDto.ChargeModeListReq) (*pricingDto.ChargeModeListRes, error) {
|
||||||
|
return pricingService.Pricing.ListChargeModes(ctx, req)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
walletDto "shop-user-trade/model/dto/wallet"
|
||||||
|
walletService "shop-user-trade/service/wallet"
|
||||||
|
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Account = new(accountController)
|
||||||
|
|
||||||
|
type accountController struct{}
|
||||||
|
|
||||||
|
// Get 获取钱包
|
||||||
|
func (c *accountController) Get(ctx context.Context, req *walletDto.GetAccountByUserIdReq) (*walletDto.AccountInfo, error) {
|
||||||
|
return walletService.Account.GetAccountByUserId(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recharge 充值
|
||||||
|
func (c *accountController) Recharge(ctx context.Context, req *walletDto.RechargeReq) (res *beans.ResponseEmpty, err error) {
|
||||||
|
err = walletService.Account.Recharge(ctx, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogs 获取钱包流水
|
||||||
|
func (c *accountController) GetLogs(ctx context.Context, req *walletDto.GetAccountLogsReq) (*walletDto.AccountLogData, error) {
|
||||||
|
return walletService.Account.GetWalletLogs(ctx, req)
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.com/red-future/common/db/gfdb"
|
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||||||
"github.com/gogf/gf/v2/database/gdb"
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
knapsackEntity "shop-user-trade/model/entity/knapsack"
|
knapsackEntity "shop-user-trade/model/entity/knapsack"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Knapsack = new(knapsackDao)
|
var Knapsack = &knapsackDao{}
|
||||||
|
|
||||||
type knapsackDao struct{}
|
type knapsackDao struct{}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.com/red-future/common/db/gfdb"
|
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||||||
"github.com/gogf/gf/v2/database/gdb"
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
marketEntity "shop-user-trade/model/entity/market"
|
marketEntity "shop-user-trade/model/entity/market"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Market = new(marketDao)
|
var Market = &marketDao{}
|
||||||
|
|
||||||
type marketDao struct{}
|
type marketDao struct{}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||||||
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
|
||||||
|
pricingConsts "shop-user-trade/consts/pricing"
|
||||||
|
"shop-user-trade/consts/public"
|
||||||
|
pricingDto "shop-user-trade/model/dto/pricing"
|
||||||
|
pricingEntity "shop-user-trade/model/entity/pricing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChargeOrder chargeOrderDao 计费单数据访问:薄查询/状态迁移原语(同 model-gateway dao 规范)。
|
||||||
|
// 方法入参统一吃 DTO(model/dto/pricing);无锁、无事务、不定义业务错误。
|
||||||
|
var ChargeOrder = &chargeOrderDao{}
|
||||||
|
|
||||||
|
type chargeOrderDao struct{}
|
||||||
|
|
||||||
|
// GetOrder 按 ID 或 subject_type+subject_id+biz_order_no 查询计费单(Id>0 走 ID)。
|
||||||
|
// 幂等查询复用:工作流断点续跑/恢复 execId 不变,据此去重。
|
||||||
|
func (d *chargeOrderDao) GetOrder(ctx context.Context, req *pricingDto.GetChargeOrderReq) (res *pricingEntity.ChargeOrder, err error) {
|
||||||
|
m := gfdb.DB(ctx, public.DbNameWallet).Model(ctx, public.TableNameChargeOrder).Model
|
||||||
|
if req.Id > 0 {
|
||||||
|
m = m.Where(pricingEntity.ChargeOrderCol.Id, req.Id)
|
||||||
|
} else {
|
||||||
|
m = m.Where(pricingEntity.ChargeOrderCol.SubjectType, req.SubjectType).
|
||||||
|
Where(pricingEntity.ChargeOrderCol.SubjectID, req.SubjectID).
|
||||||
|
Where(pricingEntity.ChargeOrderCol.BizOrderNo, req.BizOrderNo)
|
||||||
|
}
|
||||||
|
r, err := m.One()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.IsEmpty() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
err = r.Struct(&res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert 创建计费单(事务透明:在调用方事务内执行;同 model-gateway dao 规范)
|
||||||
|
func (d *chargeOrderDao) Insert(ctx context.Context, req *pricingDto.CreateChargeOrderReq) (id int64, err error) {
|
||||||
|
var e = new(pricingEntity.ChargeOrder)
|
||||||
|
err = gconv.Struct(req, &e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameWallet).Model(ctx, public.TableNameChargeOrder).Insert(e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return r.LastInsertId()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateToSettled 结算迁移:仅 CREATED → SETTLED;rows=0 表示已处理(幂等)
|
||||||
|
func (d *chargeOrderDao) UpdateToSettled(ctx context.Context, req *pricingDto.SettleChargeOrderReq) (bool, error) {
|
||||||
|
return d.migrateStatus(ctx, req.Id, gdb.Map{
|
||||||
|
pricingEntity.ChargeOrderCol.Status: int(pricingConsts.ChargeOrderStatusSettled),
|
||||||
|
pricingEntity.ChargeOrderCol.ActualAmount: req.ActualAmount,
|
||||||
|
pricingEntity.ChargeOrderCol.Usage: req.Usage,
|
||||||
|
pricingEntity.ChargeOrderCol.RuleSnapshot: req.RuleSnapshot,
|
||||||
|
pricingEntity.ChargeOrderCol.SettleTime: req.SettleTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateToFailed 失败迁移:仅 CREATED → FAILED(不扣费,不动钱)
|
||||||
|
func (d *chargeOrderDao) UpdateToFailed(ctx context.Context, req *pricingDto.FailChargeOrderReq) (bool, error) {
|
||||||
|
return d.migrateStatus(ctx, req.Id, gdb.Map{
|
||||||
|
pricingEntity.ChargeOrderCol.Status: int(pricingConsts.ChargeOrderStatusFailed),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// migrateStatus 条件状态迁移(幂等:非 CREATED 时 Update 影响 0 行返回 false)
|
||||||
|
func (d *chargeOrderDao) migrateStatus(ctx context.Context, id int64, data gdb.Map) (bool, error) {
|
||||||
|
result, err := gfdb.DB(ctx, public.DbNameWallet).Model(ctx, public.TableNameChargeOrder).
|
||||||
|
Data(data).
|
||||||
|
Where(pricingEntity.ChargeOrderCol.Id, id).
|
||||||
|
Where(pricingEntity.ChargeOrderCol.Status, int(pricingConsts.ChargeOrderStatusCreated)).
|
||||||
|
Update()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
rows, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return rows > 0, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
|
||||||
|
"shop-user-trade/consts/public"
|
||||||
|
pricingDto "shop-user-trade/model/dto/pricing"
|
||||||
|
pricingEntity "shop-user-trade/model/entity/pricing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PricingConfig pricingConfigDao 计价配置数据访问:薄 CRUD(同 model-gateway dao 规范)。
|
||||||
|
// 方法入参统一吃 DTO(model/dto/pricing);无锁、无事务、不定义业务错误。
|
||||||
|
var PricingConfig = &pricingConfigDao{}
|
||||||
|
|
||||||
|
type pricingConfigDao struct{}
|
||||||
|
|
||||||
|
// Get 按计价对象(subject_type + subject_id)获取计价配置
|
||||||
|
func (d *pricingConfigDao) Get(ctx context.Context, req *pricingDto.GetPricingConfigReq) (res *pricingEntity.PricingConfig, err error) {
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameWallet).Model(ctx, public.TableNamePricingConfig).NoTenantId(ctx).
|
||||||
|
Where(pricingEntity.PricingConfigCol.SubjectType, req.SubjectType).
|
||||||
|
Where(pricingEntity.PricingConfigCol.SubjectID, req.SubjectID).
|
||||||
|
One()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.IsEmpty() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
err = r.Struct(&res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert 插入
|
||||||
|
func (d *pricingConfigDao) Insert(ctx context.Context, req *pricingDto.SavePricingConfigReq) (id int64, err error) {
|
||||||
|
var e = new(pricingEntity.PricingConfig)
|
||||||
|
err = gconv.Struct(req, &e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameWallet).Model(ctx, public.TableNamePricingConfig).Insert(e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return r.LastInsertId()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 更新
|
||||||
|
func (d *pricingConfigDao) Update(ctx context.Context, req *pricingDto.SavePricingConfigReq) (rows int64, err error) {
|
||||||
|
// 与 Insert 一致:DTO→entity(map[string]any),Data 以 map 落 JSONB 列
|
||||||
|
var e = new(pricingEntity.PricingConfig)
|
||||||
|
if err = gconv.Struct(req, &e); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameWallet).Model(ctx, public.TableNamePricingConfig).
|
||||||
|
OmitEmpty().Data(e).Where(pricingEntity.PricingConfigCol.Id, req.Id).Update()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return r.RowsAffected()
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||||||
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Account AccountDao 钱包数据访问:薄查询/账务原语(同 model-gateway dao 规范)。
|
||||||
|
// 方法入参统一吃 DTO(model/dto/wallet);无锁、无事务、不定义业务错误。
|
||||||
|
// 锁与事务由 service 编排;钱包是否存在/是否可用的业务校验在 service 层做。
|
||||||
|
// Change 为账务原子原语,事务透明,必须由调用方在事务内执行。
|
||||||
|
var Account = &accountDao{}
|
||||||
|
|
||||||
|
type accountDao struct{}
|
||||||
|
|
||||||
|
// ====================== 查询 ======================
|
||||||
|
|
||||||
|
// Get 根据用户ID获取钱包(查询用,不加锁)
|
||||||
|
func (d *accountDao) Get(ctx context.Context, req *walletDto.GetAccountByUserIdReq) (res *walletEntity.Account, err error) {
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameWallet).Model(ctx, public.TableNameWalletAccount).
|
||||||
|
Where(walletEntity.AccountCol.UserID, req.UserId).
|
||||||
|
One()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.IsEmpty() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
err = r.Struct(&res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetForUpdate 行锁读取钱包(须在事务内调用),供账务变动使用
|
||||||
|
func (d *accountDao) GetForUpdate(ctx context.Context, req *walletDto.GetAccountByUserIdReq) (res *walletEntity.Account, err error) {
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameWallet).Model(ctx, public.TableNameWalletAccount).
|
||||||
|
Where(walletEntity.AccountCol.UserID, req.UserId).
|
||||||
|
LockUpdate().
|
||||||
|
One()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.IsEmpty() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
err = r.Struct(&res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 创建钱包(余额 0,默认启用;同 model-gateway dao 规范)
|
||||||
|
func (d *accountDao) Create(ctx context.Context, req *walletDto.CreateAccountReq) (id int64, err error) {
|
||||||
|
var e = new(walletEntity.Account)
|
||||||
|
err = gconv.Struct(req, &e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameWallet).Model(ctx, public.TableNameWalletAccount).Insert(e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return r.LastInsertId()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 原子账务 ======================
|
||||||
|
|
||||||
|
// Change 单次原子账务变动(事务透明:须在调用方事务内执行,gfdb.DB(ctx) 自动继承事务)。
|
||||||
|
// 行锁读取 → 校验状态 → 条件更新余额 → 写流水(transaction_no 唯一幂等)。
|
||||||
|
// 锁与事务由上层 service 编排(common 统一锁 + gfdb.DB(ctx).Transaction),本 DAO 不加锁、不开事务。
|
||||||
|
// ⚠️ 不做余额非负拦截:结算允许负余额(欠费,靠充值归正),见技术设计 §9。
|
||||||
|
func (d *accountDao) Change(ctx context.Context, req *walletDto.ChangeAccountReq) error {
|
||||||
|
w, err := d.GetForUpdate(ctx, &walletDto.GetAccountByUserIdReq{UserId: req.UserId})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if w == nil {
|
||||||
|
return errors.New("钱包不存在")
|
||||||
|
}
|
||||||
|
if w.Status != walletConsts.WalletStatusEnabled {
|
||||||
|
return errors.New("钱包状态不可用")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 行锁(GetForUpdate 的 FOR UPDATE)在事务内持续持有,直接以 Go 侧计算结果写回,
|
||||||
|
// 避免浮点金额走字符串拼接进 SQL 引入精度误差;余额写入 NUMERIC(15,2) 列时由 DB 四舍五入。
|
||||||
|
updateData := gdb.Map{
|
||||||
|
walletEntity.AccountCol.Balance: w.Balance + req.DeltaBalance,
|
||||||
|
}
|
||||||
|
result, err := gfdb.DB(ctx, public.DbNameWallet).Model(ctx, public.TableNameWalletAccount).
|
||||||
|
Data(updateData).
|
||||||
|
Where(walletEntity.AccountCol.Id, w.Id).
|
||||||
|
Update()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
affected, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if affected == 0 {
|
||||||
|
return errors.New("钱包余额更新失败,请重试")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = gfdb.DB(ctx, public.DbNameWallet).Model(ctx, public.TableNameWalletAccountLog).Data(&walletEntity.AccountLog{
|
||||||
|
UserID: req.UserId,
|
||||||
|
WalletID: w.Id,
|
||||||
|
OrderNo: req.OrderNo,
|
||||||
|
TransactionNo: uuid.NewString(),
|
||||||
|
Type: req.Type,
|
||||||
|
Amount: req.Amount,
|
||||||
|
BalanceBefore: w.Balance,
|
||||||
|
BalanceAfter: w.Balance + req.DeltaBalance,
|
||||||
|
Currency: w.Currency,
|
||||||
|
Description: req.Description,
|
||||||
|
ExtraData: req.ExtraData,
|
||||||
|
}).Insert()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 流水 ======================
|
||||||
|
|
||||||
|
// ListLogs 分页获取钱包流水
|
||||||
|
func (d *accountDao) ListLogs(ctx context.Context, req *walletDto.GetAccountLogsReq) (res []walletEntity.AccountLog, total int, err error) {
|
||||||
|
m := gfdb.DB(ctx, public.DbNameWallet).Model(ctx, public.TableNameWalletAccountLog).
|
||||||
|
Where(walletEntity.AccountLogCol.UserID, req.UserId).
|
||||||
|
OrderDesc(walletEntity.AccountLogCol.CreatedAt)
|
||||||
|
if req.Page != nil {
|
||||||
|
m.Page(int(req.Page.PageNum), int(req.Page.PageSize))
|
||||||
|
}
|
||||||
|
r, total, err := m.AllAndCount(false)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = r.Structs(&res)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,33 +1,42 @@
|
|||||||
module shop-user-trade
|
module shop-user-trade
|
||||||
|
|
||||||
go 1.25.8
|
go 1.26.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.com/red-future/common v0.0.7
|
gitea.redpowerfuture.com/red-future/common v0.0.33
|
||||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0
|
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2
|
||||||
github.com/gogf/gf/v2 v2.10.0
|
github.com/gogf/gf/v2 v2.10.2
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
)
|
)
|
||||||
|
|
||||||
//replace gitea.com/red-future/common v0.0.7 => ../common
|
require (
|
||||||
|
github.com/tidwall/gjson v1.18.0 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||||
github.com/armon/go-metrics v0.4.1 // indirect
|
github.com/armon/go-metrics v0.4.1 // indirect
|
||||||
github.com/bwmarrin/snowflake v0.3.0 // indirect
|
github.com/bwmarrin/snowflake v0.3.0 // indirect
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||||
|
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||||
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
|
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
|
||||||
github.com/dgraph-io/ristretto v0.1.1 // 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/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||||
github.com/fatih/color v1.19.0 // indirect
|
github.com/fatih/color v1.19.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||||
|
github.com/go-ego/gse v1.0.2 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.1 // indirect
|
github.com/goccy/go-json v0.10.6 // indirect
|
||||||
|
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2
|
||||||
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 // 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/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
@@ -36,9 +45,9 @@ require (
|
|||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/golang/snappy v1.0.0 // indirect
|
github.com/golang/snappy v1.0.0 // indirect
|
||||||
github.com/google/flatbuffers v1.12.1 // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/flatbuffers v25.12.19+incompatible // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||||
github.com/grokify/html-strip-tags-go v0.1.0 // 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/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||||
github.com/hashicorp/consul/api v1.33.5 // indirect
|
github.com/hashicorp/consul/api v1.33.5 // indirect
|
||||||
@@ -48,45 +57,45 @@ require (
|
|||||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||||
|
github.com/hashicorp/go-version v1.9.0 // indirect
|
||||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||||
github.com/hashicorp/serf v0.10.1 // indirect
|
github.com/hashicorp/serf v0.10.1 // indirect
|
||||||
github.com/klauspost/compress v1.18.4 // indirect
|
github.com/klauspost/compress v1.18.6 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/lib/pq v1.10.9 // indirect
|
github.com/lib/pq v1.12.3 // indirect
|
||||||
github.com/magiconair/properties v1.8.10 // indirect
|
github.com/magiconair/properties v1.8.10 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.24 // indirect
|
||||||
github.com/miekg/dns v1.1.72 // indirect
|
github.com/miekg/dns v1.1.72 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/olekukonko/errors v1.1.0 // indirect
|
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||||
github.com/olekukonko/ll v0.0.9 // indirect
|
github.com/olekukonko/errors v1.3.0 // indirect
|
||||||
github.com/olekukonko/tablewriter v1.1.0 // indirect
|
github.com/olekukonko/ll v0.1.8 // indirect
|
||||||
|
github.com/olekukonko/tablewriter v1.1.4 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.18.0 // indirect
|
github.com/r3labs/diff/v2 v2.15.1 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/redis/go-redis/v9 v9.21.0 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/tiger1103/gfast-token v1.0.10 // indirect
|
github.com/tiger1103/gfast-token v1.0.10 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/vcaesar/cedar v0.30.0 // indirect
|
||||||
github.com/xdg-go/scram v1.2.0 // indirect
|
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
go.mongodb.org/mongo-driver/v2 v2.6.0 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
go.opencensus.io v0.24.0 // 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/auto/sdk v1.2.1 // indirect
|
||||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
go.opentelemetry.io/otel v1.44.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.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/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.44.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
golang.org/x/crypto v0.49.0 // indirect
|
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect
|
||||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect
|
golang.org/x/net v0.56.0 // indirect
|
||||||
golang.org/x/net v0.52.0 // indirect
|
golang.org/x/sys v0.46.0 // indirect
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/text v0.38.0 // indirect
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
google.golang.org/appengine v1.6.7 // 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/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc 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/grpc v1.79.3 // indirect
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
gitea.com/red-future/common v0.0.7 h1:PbSazDCjmr+HZ4ckSSJYSCC+G/qwC5ZKbLEdsItanQ0=
|
gitea.redpowerfuture.com/red-future/common v0.0.23 h1:xieoA00iKOCDm5SO9iXn+cSyMKBAlZwI0fuEVPWrHLg=
|
||||||
gitea.com/red-future/common v0.0.7/go.mod h1:JgBczlUF8xA8S0YbvDaMLxvvpmBGgjJA6po+EbodzZA=
|
gitea.redpowerfuture.com/red-future/common v0.0.23/go.mod h1:50U1Xi+Ie56z09S5LQbZvaken0Mxv3OeS9LgR7U/ZRY=
|
||||||
|
gitea.redpowerfuture.com/red-future/common v0.0.29 h1:5McaN5pSewvrLUHQzWMX6EaUvD+B5I5bMYoU+clHJk4=
|
||||||
|
gitea.redpowerfuture.com/red-future/common v0.0.29/go.mod h1:50U1Xi+Ie56z09S5LQbZvaken0Mxv3OeS9LgR7U/ZRY=
|
||||||
|
gitea.redpowerfuture.com/red-future/common v0.0.33 h1:AhWJ6l9zrjc1U0UEfyIZu8wkkVFxNe0hfuA51vOnOIo=
|
||||||
|
gitea.redpowerfuture.com/red-future/common v0.0.33/go.mod h1:FtI9KJJSKo4/K0emjVkbL8yoSIPHJdZXr27vnScQpmM=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
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.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
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-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
@@ -36,6 +40,10 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp
|
|||||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
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/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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
||||||
|
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
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.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.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -47,8 +55,6 @@ github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWa
|
|||||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
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 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
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.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
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/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
@@ -63,8 +69,10 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
|
|||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
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 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
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.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||||
|
github.com/go-ego/gse v1.0.2 h1:+27lYFPhQEhA9igtdOsJPRKYL/k3TwYsxBF5jr6KFv4=
|
||||||
|
github.com/go-ego/gse v1.0.2/go.mod h1:Fy35G+q7VV7Et1zIKO8o/sW1kkugV3znXap/lF/11zc=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
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-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.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
@@ -75,18 +83,20 @@ github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4
|
|||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
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.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0 h1:39+jbTenm7KBj4hO2C8ANAxVHpX/7OuRDs1VcGC9ylA=
|
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0/go.mod h1:B0s0fVzn0W220E8UTpSGzrrGKsop5KcB90twBeLCiz0=
|
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.1 h1:egobo4YfQX3C4NtrEFunBqMX3jsddagklgut9u91+BM=
|
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2 h1:u8EpP24GkprogROnJ7htMov9Fc66pTP1eVYrWxiCYOs=
|
||||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.1/go.mod h1:YQ+u5Cs5N2ETCeQaLbv29z/UWYuxw27mJpUkOLj0kJ8=
|
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2/go.mod h1:GmvM3r8GVByVMi4RD2+MCs5+CfxVXPMeT8mVDkAaAXE=
|
||||||
|
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2 h1:iTQegT+lEg/wDKvj2mi3W1wrdrwFarjokf88EXVVgu4=
|
||||||
|
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2/go.mod h1:ZRw3GNz5cq4uYrW4TPSVyrYWaoqzujKdWro/AOcGBaE=
|
||||||
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 h1:eUqwJ/qNH8lJ6yssiqskazgp1ACQuNU6zXlLOZVuXTQ=
|
||||||
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5/go.mod h1:sjQyMry9+0POYZCA6lHXBxO77WoNKkruJpRB4xKqk5k=
|
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 h1:tHUEZYB5GTqEYYVDYnlGobf1xISARKDE4KHVlgjwTec=
|
||||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5/go.mod h1:cfzTn2HS9RDX8f5pUVkbGxUWcSosouqfNQ1G6cY0V88=
|
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.2 h1:46IO0Uc8e85/FqdftJFskfDejJLBL0JBnGS5qOftUu8=
|
||||||
github.com/gogf/gf/v2 v2.10.0/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
|
github.com/gogf/gf/v2 v2.10.2/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.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 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
@@ -114,10 +124,10 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
|
|||||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
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 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.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
|
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
|
||||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
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.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.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
@@ -130,8 +140,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
|||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||||
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 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
|
||||||
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
|
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 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||||
@@ -139,7 +149,6 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l8
|
|||||||
github.com/hashicorp/consul/api v1.33.5 h1:Nn6q87zudRU1rLBTJEgaWxz9STCNadilLCD7B8OA5aI=
|
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/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 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.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 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@@ -169,8 +178,8 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
|
|||||||
github.com/hashicorp/go-uuid v1.0.1/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 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
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.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA=
|
||||||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
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 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
|
||||||
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
@@ -185,8 +194,8 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
|
|||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
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/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
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.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
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/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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
@@ -198,8 +207,8 @@ 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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
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.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
@@ -207,17 +216,17 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
|
|||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
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.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.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.15/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.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.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.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.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.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.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
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.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.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||||
@@ -232,12 +241,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
|||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
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/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/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/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
|
||||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
|
||||||
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
github.com/olekukonko/errors v1.3.0 h1:teJvgLGUEqMzBUms+Dj3/3szNqCG/Jdw9iDbum8fR6U=
|
||||||
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
github.com/olekukonko/errors v1.3.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||||
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
|
github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8=
|
||||||
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw=
|
||||||
|
github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
|
||||||
|
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
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 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
@@ -262,13 +273,11 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b
|
|||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
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/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg=
|
||||||
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/redis/go-redis/v9 v9.21.0 h1:FPBE4hhbAke+TLmcY3WkpbDffJEomdqPn3HYiqAtL9E=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/redis/go-redis/v9 v9.21.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||||
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 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/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 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
@@ -276,51 +285,61 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
|||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
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.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.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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.7.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.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
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/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
github.com/tiger1103/gfast-token v1.0.10 h1:fNiBE/Dq5iTHvTGlCx3DmXa2o4hr0NtumFpffZ39k6s=
|
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/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/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
github.com/vcaesar/cedar v0.30.0 h1:9fSDpM7FTjjUdPiBUUa0MWYMRGSEcqgFXvppZcZ4d7Y=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
github.com/vcaesar/cedar v0.30.0/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFezNsnik=
|
||||||
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
|
github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
|
||||||
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
|
github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU=
|
||||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
|
||||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
go.mongodb.org/mongo-driver/v2 v2.6.0 h1:b9sJOYrkmt4l8bY43ZenFBcPlhYIjaOfYHLtbB/5qi8=
|
||||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
go.mongodb.org/mongo-driver/v2 v2.6.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
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 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
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.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
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 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 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/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.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||||
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 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
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 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
@@ -332,26 +351,22 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
|
||||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
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-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
|
||||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
|
golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||||
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-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-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-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-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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
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-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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@@ -360,9 +375,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||||
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/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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -371,9 +385,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||||
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-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-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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -392,27 +404,20 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/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-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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-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-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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-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.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.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||||
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-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-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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
@@ -422,9 +427,7 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/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-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.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk=
|
||||||
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-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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -433,6 +436,9 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
|||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
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.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||||
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
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-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
|||||||
@@ -1,164 +1,423 @@
|
|||||||
-- shop_user_trade 数据库初始化脚本
|
-----------shop-user-trade 2026-08-28 12:00:00--------------
|
||||||
|
|
||||||
|
--------------------pgsql创建knapsack表语句---------------------------
|
||||||
|
|
||||||
-- 背包表
|
-- 背包表
|
||||||
CREATE TABLE IF NOT EXISTS knapsack (
|
CREATE TABLE IF NOT EXISTS knapsack (
|
||||||
id BIGINT PRIMARY KEY,
|
-- 基础字段(继承 SQLBaseCol 通用字段,与 SQLBaseDO 对齐)
|
||||||
tenant_id BIGINT NOT NULL DEFAULT 0,
|
id BIGINT PRIMARY KEY,
|
||||||
creator VARCHAR(64) NOT NULL DEFAULT '',
|
tenant_id BIGINT NOT NULL DEFAULT 0,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
creator VARCHAR(64) NOT NULL,
|
||||||
updater VARCHAR(64) NOT NULL DEFAULT '',
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
updater VARCHAR(64) NOT NULL,
|
||||||
deleted_at TIMESTAMPTZ,
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
deleted_at timestamp(6),
|
||||||
|
|
||||||
user_id BIGINT NOT NULL,
|
-- 背包核心字段
|
||||||
asset_id BIGINT NOT NULL,
|
user_id BIGINT NOT NULL,
|
||||||
asset_name VARCHAR(255) NOT NULL DEFAULT '',
|
asset_id BIGINT NOT NULL,
|
||||||
sku_id BIGINT NOT NULL DEFAULT 0,
|
asset_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
sku_name VARCHAR(255) NOT NULL DEFAULT '',
|
sku_id BIGINT NOT NULL DEFAULT 0,
|
||||||
spec_values JSONB,
|
sku_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
image_url VARCHAR(512) NOT NULL DEFAULT '',
|
spec_values JSONB,
|
||||||
type VARCHAR(32) NOT NULL,
|
image_url VARCHAR(512) NOT NULL DEFAULT '',
|
||||||
stock_detail_id BIGINT NOT NULL DEFAULT 0,
|
type VARCHAR(32) NOT NULL,
|
||||||
batch_id BIGINT NOT NULL DEFAULT 0,
|
stock_detail_id BIGINT NOT NULL DEFAULT 0,
|
||||||
batch_no VARCHAR(64) NOT NULL DEFAULT '',
|
batch_id BIGINT NOT NULL DEFAULT 0,
|
||||||
stock_mode SMALLINT NOT NULL DEFAULT 1,
|
batch_no VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
status SMALLINT NOT NULL DEFAULT 1,
|
stock_mode SMALLINT NOT NULL DEFAULT 1,
|
||||||
used_at BIGINT,
|
status SMALLINT NOT NULL DEFAULT 1,
|
||||||
expire_at BIGINT,
|
used_at BIGINT,
|
||||||
|
expire_at BIGINT,
|
||||||
physical_asset_config JSONB,
|
physical_asset_config JSONB,
|
||||||
service_asset_config JSONB,
|
service_asset_config JSONB,
|
||||||
virtual_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_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_asset_id ON knapsack(asset_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_knapsack_status ON knapsack(status);
|
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 INDEX IF NOT EXISTS idx_knapsack_tenant_id ON knapsack(tenant_id);
|
||||||
|
|
||||||
|
-- 表和字段注释
|
||||||
|
COMMENT ON TABLE knapsack IS '背包表';
|
||||||
|
COMMENT ON COLUMN knapsack.id IS '主键ID(非自增)';
|
||||||
|
COMMENT ON COLUMN knapsack.tenant_id IS '租户ID';
|
||||||
|
COMMENT ON COLUMN knapsack.creator IS '创建人';
|
||||||
|
COMMENT ON COLUMN knapsack.created_at IS '创建时间';
|
||||||
|
COMMENT ON COLUMN knapsack.updater IS '更新人';
|
||||||
|
COMMENT ON COLUMN knapsack.updated_at IS '更新时间';
|
||||||
|
COMMENT ON COLUMN knapsack.deleted_at IS '删除时间(软删)';
|
||||||
|
COMMENT ON COLUMN knapsack.user_id IS '用户ID';
|
||||||
|
COMMENT ON COLUMN knapsack.asset_id IS '资产ID';
|
||||||
|
COMMENT ON COLUMN knapsack.asset_name IS '资产名称';
|
||||||
|
COMMENT ON COLUMN knapsack.sku_id IS 'SKU ID';
|
||||||
|
COMMENT ON COLUMN knapsack.sku_name IS 'SKU名称';
|
||||||
|
COMMENT ON COLUMN knapsack.spec_values IS '规格值(JSONB)';
|
||||||
|
COMMENT ON COLUMN knapsack.image_url IS '资产图片URL';
|
||||||
|
COMMENT ON COLUMN knapsack.type IS '资产类型:physical/virtual/service';
|
||||||
|
COMMENT ON COLUMN knapsack.stock_detail_id IS '库存明细ID(明细模式)';
|
||||||
|
COMMENT ON COLUMN knapsack.batch_id IS '批次ID(批次模式)';
|
||||||
|
COMMENT ON COLUMN knapsack.batch_no IS '批次号(批次模式)';
|
||||||
|
COMMENT ON COLUMN knapsack.stock_mode IS '库存管理模式:1-明细模式,2-批次模式';
|
||||||
|
COMMENT ON COLUMN knapsack.status IS '状态:0禁用/1启用/2已使用/3已上架/4已过期';
|
||||||
|
COMMENT ON COLUMN knapsack.used_at IS '使用时间(时间戳)';
|
||||||
|
COMMENT ON COLUMN knapsack.expire_at IS '过期时间(时间戳)';
|
||||||
|
COMMENT ON COLUMN knapsack.physical_asset_config IS '实物资产配置(JSONB)';
|
||||||
|
COMMENT ON COLUMN knapsack.service_asset_config IS '服务资产配置(JSONB)';
|
||||||
|
COMMENT ON COLUMN knapsack.virtual_asset_config IS '虚拟资产配置(JSONB)';
|
||||||
|
|
||||||
|
--------------------pgsql创建knapsack表语句---------------------------
|
||||||
|
|
||||||
|
--------------------pgsql创建knapsack_log表语句---------------------------
|
||||||
|
|
||||||
-- 背包日志表
|
-- 背包日志表
|
||||||
CREATE TABLE IF NOT EXISTS knapsack_log (
|
CREATE TABLE IF NOT EXISTS knapsack_log (
|
||||||
id BIGINT PRIMARY KEY,
|
-- 基础字段(继承 SQLBaseCol 通用字段,与 SQLBaseDO 对齐)
|
||||||
tenant_id BIGINT NOT NULL DEFAULT 0,
|
id BIGINT PRIMARY KEY,
|
||||||
creator VARCHAR(64) NOT NULL DEFAULT '',
|
tenant_id BIGINT NOT NULL DEFAULT 0,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
creator VARCHAR(64) NOT NULL,
|
||||||
updater VARCHAR(64) NOT NULL DEFAULT '',
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
updater VARCHAR(64) NOT NULL,
|
||||||
deleted_at TIMESTAMPTZ,
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
deleted_at timestamp(6),
|
||||||
|
|
||||||
knapsack_id BIGINT NOT NULL,
|
-- 日志核心字段
|
||||||
user_id BIGINT NOT NULL,
|
knapsack_id BIGINT NOT NULL,
|
||||||
action VARCHAR(32) NOT NULL,
|
user_id BIGINT NOT NULL,
|
||||||
operator_id BIGINT NOT NULL DEFAULT 0,
|
action VARCHAR(32) NOT NULL,
|
||||||
operator_name VARCHAR(64) NOT NULL DEFAULT '',
|
operator_id BIGINT NOT NULL DEFAULT 0,
|
||||||
description TEXT NOT NULL DEFAULT ''
|
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 INDEX IF NOT EXISTS idx_knapsack_log_knapsack_id ON knapsack_log(knapsack_id);
|
||||||
|
|
||||||
|
-- 表和字段注释
|
||||||
|
COMMENT ON TABLE knapsack_log IS '背包日志表';
|
||||||
|
COMMENT ON COLUMN knapsack_log.id IS '主键ID(非自增)';
|
||||||
|
COMMENT ON COLUMN knapsack_log.tenant_id IS '租户ID';
|
||||||
|
COMMENT ON COLUMN knapsack_log.creator IS '创建人';
|
||||||
|
COMMENT ON COLUMN knapsack_log.created_at IS '创建时间';
|
||||||
|
COMMENT ON COLUMN knapsack_log.updater IS '更新人';
|
||||||
|
COMMENT ON COLUMN knapsack_log.updated_at IS '更新时间';
|
||||||
|
COMMENT ON COLUMN knapsack_log.deleted_at IS '删除时间(软删)';
|
||||||
|
COMMENT ON COLUMN knapsack_log.knapsack_id IS '背包项ID';
|
||||||
|
COMMENT ON COLUMN knapsack_log.user_id IS '用户ID';
|
||||||
|
COMMENT ON COLUMN knapsack_log.action IS '操作类型';
|
||||||
|
COMMENT ON COLUMN knapsack_log.operator_id IS '操作者ID';
|
||||||
|
COMMENT ON COLUMN knapsack_log.operator_name IS '操作者名称';
|
||||||
|
COMMENT ON COLUMN knapsack_log.description IS '操作描述';
|
||||||
|
|
||||||
|
--------------------pgsql创建knapsack_log表语句---------------------------
|
||||||
|
|
||||||
|
--------------------pgsql创建market表语句---------------------------
|
||||||
|
|
||||||
-- 市场表
|
-- 市场表
|
||||||
CREATE TABLE IF NOT EXISTS market (
|
CREATE TABLE IF NOT EXISTS market (
|
||||||
id BIGINT PRIMARY KEY,
|
-- 基础字段(继承 SQLBaseCol 通用字段,与 SQLBaseDO 对齐)
|
||||||
tenant_id BIGINT NOT NULL DEFAULT 0,
|
id BIGINT PRIMARY KEY,
|
||||||
creator VARCHAR(64) NOT NULL DEFAULT '',
|
tenant_id BIGINT NOT NULL DEFAULT 0,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
creator VARCHAR(64) NOT NULL,
|
||||||
updater VARCHAR(64) NOT NULL DEFAULT '',
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
updater VARCHAR(64) NOT NULL,
|
||||||
deleted_at TIMESTAMPTZ,
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
deleted_at timestamp(6),
|
||||||
|
|
||||||
knapsack_id BIGINT NOT NULL,
|
-- 市场核心字段
|
||||||
user_id BIGINT NOT NULL,
|
knapsack_id BIGINT NOT NULL,
|
||||||
asset_id BIGINT NOT NULL,
|
user_id BIGINT NOT NULL,
|
||||||
asset_name VARCHAR(255) NOT NULL DEFAULT '',
|
asset_id BIGINT NOT NULL,
|
||||||
sku_id BIGINT NOT NULL DEFAULT 0,
|
asset_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
sku_name VARCHAR(255) NOT NULL DEFAULT '',
|
sku_id BIGINT NOT NULL DEFAULT 0,
|
||||||
image_url VARCHAR(512) NOT NULL DEFAULT '',
|
sku_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
type VARCHAR(32) NOT NULL,
|
image_url VARCHAR(512) NOT NULL DEFAULT '',
|
||||||
stock_detail_id BIGINT NOT NULL DEFAULT 0,
|
type VARCHAR(32) NOT NULL,
|
||||||
batch_id BIGINT NOT NULL DEFAULT 0,
|
stock_detail_id BIGINT NOT NULL DEFAULT 0,
|
||||||
batch_no VARCHAR(64) NOT NULL DEFAULT '',
|
batch_id BIGINT NOT NULL DEFAULT 0,
|
||||||
stock_mode SMALLINT NOT NULL DEFAULT 1,
|
batch_no VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
price BIGINT NOT NULL DEFAULT 0,
|
stock_mode SMALLINT NOT NULL DEFAULT 1,
|
||||||
original_price BIGINT NOT NULL DEFAULT 0,
|
price BIGINT NOT NULL DEFAULT 0,
|
||||||
description TEXT NOT NULL DEFAULT '',
|
original_price BIGINT NOT NULL DEFAULT 0,
|
||||||
status SMALLINT NOT NULL DEFAULT 1,
|
description TEXT NOT NULL DEFAULT '',
|
||||||
|
status SMALLINT NOT NULL DEFAULT 1,
|
||||||
list_expire_at BIGINT
|
list_expire_at BIGINT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- 索引
|
||||||
CREATE INDEX IF NOT EXISTS idx_market_user_id ON market(user_id);
|
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_knapsack_id ON market(knapsack_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_market_status ON market(status);
|
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 INDEX IF NOT EXISTS idx_market_tenant_id ON market(tenant_id);
|
||||||
|
|
||||||
|
-- 表和字段注释
|
||||||
|
COMMENT ON TABLE market IS '市场表';
|
||||||
|
COMMENT ON COLUMN market.id IS '主键ID(非自增)';
|
||||||
|
COMMENT ON COLUMN market.tenant_id IS '租户ID';
|
||||||
|
COMMENT ON COLUMN market.creator IS '创建人';
|
||||||
|
COMMENT ON COLUMN market.created_at IS '创建时间';
|
||||||
|
COMMENT ON COLUMN market.updater IS '更新人';
|
||||||
|
COMMENT ON COLUMN market.updated_at IS '更新时间';
|
||||||
|
COMMENT ON COLUMN market.deleted_at IS '删除时间(软删)';
|
||||||
|
COMMENT ON COLUMN market.knapsack_id IS '背包项ID';
|
||||||
|
COMMENT ON COLUMN market.user_id IS '卖家用户ID';
|
||||||
|
COMMENT ON COLUMN market.asset_id IS '资产ID';
|
||||||
|
COMMENT ON COLUMN market.asset_name IS '资产名称';
|
||||||
|
COMMENT ON COLUMN market.sku_id IS 'SKU ID';
|
||||||
|
COMMENT ON COLUMN market.sku_name IS 'SKU名称';
|
||||||
|
COMMENT ON COLUMN market.image_url IS '资产图片URL';
|
||||||
|
COMMENT ON COLUMN market.type IS '资产类型:physical/virtual/service';
|
||||||
|
COMMENT ON COLUMN market.stock_detail_id IS '库存明细ID(明细模式)';
|
||||||
|
COMMENT ON COLUMN market.batch_id IS '批次ID(批次模式)';
|
||||||
|
COMMENT ON COLUMN market.batch_no IS '批次号(批次模式)';
|
||||||
|
COMMENT ON COLUMN market.stock_mode IS '库存管理模式:1-明细模式,2-批次模式';
|
||||||
|
COMMENT ON COLUMN market.price IS '售价(分)';
|
||||||
|
COMMENT ON COLUMN market.original_price IS '原价(分)';
|
||||||
|
COMMENT ON COLUMN market.description IS '描述信息';
|
||||||
|
COMMENT ON COLUMN market.status IS '状态:1活跃/2已售/3下架/4过期';
|
||||||
|
COMMENT ON COLUMN market.list_expire_at IS '上架过期时间(时间戳)';
|
||||||
|
|
||||||
|
--------------------pgsql创建market表语句---------------------------
|
||||||
|
|
||||||
|
--------------------pgsql创建market_log表语句---------------------------
|
||||||
|
|
||||||
-- 市场日志表
|
-- 市场日志表
|
||||||
CREATE TABLE IF NOT EXISTS market_log (
|
CREATE TABLE IF NOT EXISTS market_log (
|
||||||
id BIGINT PRIMARY KEY,
|
-- 基础字段(继承 SQLBaseCol 通用字段,与 SQLBaseDO 对齐)
|
||||||
tenant_id BIGINT NOT NULL DEFAULT 0,
|
id BIGINT PRIMARY KEY,
|
||||||
creator VARCHAR(64) NOT NULL DEFAULT '',
|
tenant_id BIGINT NOT NULL DEFAULT 0,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
creator VARCHAR(64) NOT NULL,
|
||||||
updater VARCHAR(64) NOT NULL DEFAULT '',
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
updater VARCHAR(64) NOT NULL,
|
||||||
deleted_at TIMESTAMPTZ,
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
deleted_at timestamp(6),
|
||||||
|
|
||||||
market_id BIGINT NOT NULL,
|
-- 日志核心字段
|
||||||
knapsack_id BIGINT NOT NULL,
|
market_id BIGINT NOT NULL,
|
||||||
user_id BIGINT NOT NULL,
|
knapsack_id BIGINT NOT NULL,
|
||||||
action VARCHAR(32) NOT NULL,
|
user_id BIGINT NOT NULL,
|
||||||
operator_id BIGINT NOT NULL DEFAULT 0,
|
action VARCHAR(32) NOT NULL,
|
||||||
operator_name VARCHAR(64) NOT NULL DEFAULT '',
|
operator_id BIGINT NOT NULL DEFAULT 0,
|
||||||
description TEXT NOT NULL DEFAULT ''
|
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 INDEX IF NOT EXISTS idx_market_log_market_id ON market_log(market_id);
|
||||||
|
|
||||||
-- 钱包表
|
-- 表和字段注释
|
||||||
CREATE TABLE IF NOT EXISTS wallet (
|
COMMENT ON TABLE market_log IS '市场日志表';
|
||||||
id BIGINT PRIMARY KEY,
|
COMMENT ON COLUMN market_log.id IS '主键ID(非自增)';
|
||||||
tenant_id BIGINT NOT NULL DEFAULT 0,
|
COMMENT ON COLUMN market_log.tenant_id IS '租户ID';
|
||||||
creator VARCHAR(64) NOT NULL DEFAULT '',
|
COMMENT ON COLUMN market_log.creator IS '创建人';
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
COMMENT ON COLUMN market_log.created_at IS '创建时间';
|
||||||
updater VARCHAR(64) NOT NULL DEFAULT '',
|
COMMENT ON COLUMN market_log.updater IS '更新人';
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
COMMENT ON COLUMN market_log.updated_at IS '更新时间';
|
||||||
deleted_at TIMESTAMPTZ,
|
COMMENT ON COLUMN market_log.deleted_at IS '删除时间(软删)';
|
||||||
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
COMMENT ON COLUMN market_log.market_id IS '市场物品ID';
|
||||||
|
COMMENT ON COLUMN market_log.knapsack_id IS '背包项ID';
|
||||||
|
COMMENT ON COLUMN market_log.user_id IS '用户ID';
|
||||||
|
COMMENT ON COLUMN market_log.action IS '操作类型';
|
||||||
|
COMMENT ON COLUMN market_log.operator_id IS '操作者ID';
|
||||||
|
COMMENT ON COLUMN market_log.operator_name IS '操作者名称';
|
||||||
|
COMMENT ON COLUMN market_log.description IS '操作描述';
|
||||||
|
|
||||||
user_id BIGINT NOT NULL UNIQUE,
|
--------------------pgsql创建market_log表语句---------------------------
|
||||||
balance BIGINT NOT NULL DEFAULT 0,
|
|
||||||
currency VARCHAR(16) NOT NULL DEFAULT 'CNY',
|
--------------------pgsql创建wallet_account表语句---------------------------
|
||||||
status SMALLINT NOT NULL DEFAULT 1,
|
|
||||||
version BIGINT NOT NULL DEFAULT 1
|
-- 钱包表
|
||||||
|
CREATE TABLE IF NOT EXISTS wallet_account (
|
||||||
|
-- 基础字段(继承 SQLBaseCol 通用字段,与 SQLBaseDO 对齐)
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
tenant_id BIGINT NOT NULL DEFAULT 0,
|
||||||
|
creator VARCHAR(64) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updater VARCHAR(64) NOT NULL,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at timestamp(6),
|
||||||
|
|
||||||
|
-- 钱包核心字段
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
user_name VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
balance NUMERIC(15,2) NOT NULL DEFAULT 0,
|
||||||
|
currency VARCHAR(16) NOT NULL DEFAULT 'CNY',
|
||||||
|
status SMALLINT 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 UNIQUE INDEX IF NOT EXISTS uk_account_user_id ON wallet_account(user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_account_user_id ON wallet_account(user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_account_tenant_id ON wallet_account(tenant_id);
|
||||||
|
|
||||||
|
-- 表和字段注释
|
||||||
|
COMMENT ON TABLE wallet_account IS '钱包表';
|
||||||
|
COMMENT ON COLUMN wallet_account.id IS '主键ID(非自增)';
|
||||||
|
COMMENT ON COLUMN wallet_account.tenant_id IS '租户ID';
|
||||||
|
COMMENT ON COLUMN wallet_account.creator IS '创建人';
|
||||||
|
COMMENT ON COLUMN wallet_account.created_at IS '创建时间';
|
||||||
|
COMMENT ON COLUMN wallet_account.updater IS '更新人';
|
||||||
|
COMMENT ON COLUMN wallet_account.updated_at IS '更新时间';
|
||||||
|
COMMENT ON COLUMN wallet_account.deleted_at IS '删除时间(软删)';
|
||||||
|
COMMENT ON COLUMN wallet_account.user_id IS '用户ID(唯一)';
|
||||||
|
COMMENT ON COLUMN wallet_account.user_name IS '用户名';
|
||||||
|
COMMENT ON COLUMN wallet_account.balance IS '可用余额(元,保留2位小数),可为负=欠费';
|
||||||
|
COMMENT ON COLUMN wallet_account.currency IS '货币类型:CNY-人民币';
|
||||||
|
COMMENT ON COLUMN wallet_account.status IS '状态:1启用/0禁用/-1冻结';
|
||||||
|
|
||||||
|
--------------------pgsql创建wallet_account表语句---------------------------
|
||||||
|
|
||||||
|
--------------------pgsql创建wallet_account_log表语句---------------------------
|
||||||
|
|
||||||
-- 钱包日志表
|
-- 钱包日志表
|
||||||
CREATE TABLE IF NOT EXISTS wallet_log (
|
CREATE TABLE IF NOT EXISTS wallet_account_log (
|
||||||
id BIGINT PRIMARY KEY,
|
-- 基础字段(继承 SQLBaseCol 通用字段,与 SQLBaseDO 对齐)
|
||||||
tenant_id BIGINT NOT NULL DEFAULT 0,
|
id BIGINT PRIMARY KEY,
|
||||||
creator VARCHAR(64) NOT NULL DEFAULT '',
|
tenant_id BIGINT NOT NULL DEFAULT 0,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
creator VARCHAR(64) NOT NULL,
|
||||||
updater VARCHAR(64) NOT NULL DEFAULT '',
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
updater VARCHAR(64) NOT NULL,
|
||||||
deleted_at TIMESTAMPTZ,
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
deleted_at timestamp(6),
|
||||||
|
|
||||||
user_id BIGINT NOT NULL,
|
-- 流水核心字段
|
||||||
wallet_id BIGINT NOT NULL,
|
user_id BIGINT NOT NULL,
|
||||||
order_no VARCHAR(64) NOT NULL DEFAULT '',
|
wallet_id BIGINT NOT NULL,
|
||||||
transaction_no VARCHAR(64) NOT NULL DEFAULT '',
|
order_no VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
type VARCHAR(16) NOT NULL,
|
transaction_no VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
amount BIGINT NOT NULL DEFAULT 0,
|
type VARCHAR(16) NOT NULL,
|
||||||
balance_before BIGINT NOT NULL DEFAULT 0,
|
amount NUMERIC(15,2) NOT NULL DEFAULT 0,
|
||||||
balance_after BIGINT NOT NULL DEFAULT 0,
|
balance_before NUMERIC(15,2) NOT NULL DEFAULT 0,
|
||||||
currency VARCHAR(16) NOT NULL DEFAULT 'CNY',
|
balance_after NUMERIC(15,2) NOT NULL DEFAULT 0,
|
||||||
description TEXT NOT NULL DEFAULT '',
|
currency VARCHAR(16) NOT NULL DEFAULT 'CNY',
|
||||||
extra_data JSONB
|
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_account_log_user_id ON wallet_account_log(user_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_wallet_log_order_no ON wallet_log(order_no);
|
CREATE INDEX IF NOT EXISTS idx_account_log_wallet_id ON wallet_account_log(wallet_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_account_log_order_no ON wallet_account_log(order_no);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_account_log_transaction ON wallet_account_log(transaction_no) WHERE transaction_no <> '';
|
||||||
|
|
||||||
|
-- 表和字段注释
|
||||||
|
COMMENT ON TABLE wallet_account_log IS '钱包日志表';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.id IS '主键ID(非自增)';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.tenant_id IS '租户ID';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.creator IS '创建人';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.created_at IS '创建时间';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.updater IS '更新人';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.updated_at IS '更新时间';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.deleted_at IS '删除时间(软删)';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.user_id IS '用户ID';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.wallet_id IS '钱包ID';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.order_no IS '业务单据号(计费单号/充值单号)';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.transaction_no IS '账务流水号(唯一,幂等)';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.type IS '账务类型:income/expense';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.amount IS '本次变动金额(元,保留2位小数,正数)';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.balance_before IS '可用余额变动前(元)';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.balance_after IS '可用余额变动后(元)';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.currency IS '货币类型';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.description IS '描述';
|
||||||
|
COMMENT ON COLUMN wallet_account_log.extra_data IS '额外数据(JSONB)';
|
||||||
|
|
||||||
|
--------------------pgsql创建wallet_account_log表语句---------------------------
|
||||||
|
|
||||||
|
--------------------pgsql创建wallet_pricing_config表语句---------------------------
|
||||||
|
|
||||||
|
-- 计价配置表(费率全 DB 配置,改价/加档只改 rules,代码不写死金额)
|
||||||
|
CREATE TABLE IF NOT EXISTS wallet_pricing_config (
|
||||||
|
-- 基础字段(继承 SQLBaseCol 通用字段,与 SQLBaseDO 对齐)
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
tenant_id BIGINT NOT NULL DEFAULT 0,
|
||||||
|
creator VARCHAR(64) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updater VARCHAR(64) NOT NULL,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at timestamp(6),
|
||||||
|
|
||||||
|
-- 计价配置核心字段
|
||||||
|
subject_type VARCHAR(16) NOT NULL, -- workflow | model | business
|
||||||
|
subject_id VARCHAR(64) NOT NULL, -- workflow | 模型ID | 业务模块标识
|
||||||
|
rules JSONB NOT NULL DEFAULT '{}', -- 费率JSON对象(见设计§4,rules 自描述)
|
||||||
|
min_balance NUMERIC(15,2) NOT NULL DEFAULT 0,
|
||||||
|
currency VARCHAR(16) NOT NULL DEFAULT 'CNY',
|
||||||
|
enabled SMALLINT NOT NULL DEFAULT 1
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 索引
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_pricing_config_subject ON wallet_pricing_config(subject_type, subject_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_pricing_config_enabled ON wallet_pricing_config(enabled);
|
||||||
|
|
||||||
|
-- 表和字段注释
|
||||||
|
COMMENT ON TABLE wallet_pricing_config IS '计价配置表(计价对象固定枚举,费率全 DB 配置)';
|
||||||
|
COMMENT ON COLUMN wallet_pricing_config.id IS '主键ID(非自增)';
|
||||||
|
COMMENT ON COLUMN wallet_pricing_config.tenant_id IS '租户ID';
|
||||||
|
COMMENT ON COLUMN wallet_pricing_config.creator IS '创建人';
|
||||||
|
COMMENT ON COLUMN wallet_pricing_config.created_at IS '创建时间';
|
||||||
|
COMMENT ON COLUMN wallet_pricing_config.updater IS '更新人';
|
||||||
|
COMMENT ON COLUMN wallet_pricing_config.updated_at IS '更新时间';
|
||||||
|
COMMENT ON COLUMN wallet_pricing_config.deleted_at IS '删除时间(软删)';
|
||||||
|
COMMENT ON COLUMN wallet_pricing_config.subject_type IS '计价对象类型:workflow/model/business';
|
||||||
|
COMMENT ON COLUMN wallet_pricing_config.subject_id IS '计价对象ID(workflow/模型ID/业务模块标识)';
|
||||||
|
COMMENT ON COLUMN wallet_pricing_config.rules IS '费率JSON(workflow三模式容器/model自适应/business周期)';
|
||||||
|
COMMENT ON COLUMN wallet_pricing_config.min_balance IS '门禁:调用前可用余额须>=该值(元),0=不校验';
|
||||||
|
COMMENT ON COLUMN wallet_pricing_config.currency IS '货币类型';
|
||||||
|
COMMENT ON COLUMN wallet_pricing_config.enabled IS '1启用 0停用';
|
||||||
|
|
||||||
|
--------------------pgsql创建wallet_pricing_config表语句---------------------------
|
||||||
|
|
||||||
|
--------------------pgsql创建wallet_charge_order表语句---------------------------
|
||||||
|
|
||||||
|
-- 计费单表(一次计价调用 = 一张计费单,幂等键 subject_type + subject_id + biz_order_no)
|
||||||
|
CREATE TABLE IF NOT EXISTS wallet_charge_order (
|
||||||
|
-- 基础字段(继承 SQLBaseCol 通用字段,与 SQLBaseDO 对齐)
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
tenant_id BIGINT NOT NULL DEFAULT 0,
|
||||||
|
creator VARCHAR(64) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updater VARCHAR(64) NOT NULL,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at timestamp(6),
|
||||||
|
|
||||||
|
-- 计费单核心字段
|
||||||
|
subject_type VARCHAR(16) NOT NULL,
|
||||||
|
subject_id VARCHAR(64) NOT NULL,
|
||||||
|
biz_order_no VARCHAR(128) NOT NULL,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
charge_mode VARCHAR(32) NOT NULL,
|
||||||
|
status SMALLINT NOT NULL DEFAULT 1,
|
||||||
|
actual_amount NUMERIC(15,2) NOT NULL DEFAULT 0,
|
||||||
|
usage JSONB NOT NULL DEFAULT '{}',
|
||||||
|
rule_snapshot JSONB NOT NULL DEFAULT '{}',
|
||||||
|
settle_time TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 索引
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_charge_order_subject ON wallet_charge_order(subject_type, subject_id, biz_order_no);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_charge_order_user_id ON wallet_charge_order(user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_charge_order_status ON wallet_charge_order(status);
|
||||||
|
|
||||||
|
-- 表和字段注释
|
||||||
|
COMMENT ON TABLE wallet_charge_order IS '计费单表(一次计价调用 = 一张计费单)';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.id IS '主键ID(非自增)';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.tenant_id IS '租户ID';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.creator IS '创建人';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.created_at IS '创建时间';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.updater IS '更新人';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.updated_at IS '更新时间';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.deleted_at IS '删除时间(软删)';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.subject_type IS '计价对象类型';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.subject_id IS '计价对象ID';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.biz_order_no IS '业务单据号(幂等键,如工作流 execId)';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.user_id IS '扣费用户ID';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.charge_mode IS '计费方式(建单快照)';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.status IS '状态:1已建单 2已结算 3已失败';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.actual_amount IS '实收金额(元,保留2位小数)';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.usage IS '实际用量JSON';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.rule_snapshot IS '结算用费率快照JSON(防改价影响在途单)';
|
||||||
|
COMMENT ON COLUMN wallet_charge_order.settle_time IS '结算时间';
|
||||||
|
|
||||||
|
--------------------pgsql创建wallet_charge_order表语句---------------------------
|
||||||
@@ -5,12 +5,14 @@ import (
|
|||||||
|
|
||||||
knapsackController "shop-user-trade/controller/knapsack"
|
knapsackController "shop-user-trade/controller/knapsack"
|
||||||
marketController "shop-user-trade/controller/market"
|
marketController "shop-user-trade/controller/market"
|
||||||
|
pricingController "shop-user-trade/controller/pricing"
|
||||||
walletController "shop-user-trade/controller/wallet"
|
walletController "shop-user-trade/controller/wallet"
|
||||||
|
|
||||||
"gitea.com/red-future/common/http"
|
"gitea.redpowerfuture.com/red-future/common/http"
|
||||||
"gitea.com/red-future/common/jaeger"
|
"gitea.redpowerfuture.com/red-future/common/jaeger"
|
||||||
_ "gitea.com/red-future/common/swagger"
|
_ "gitea.redpowerfuture.com/red-future/common/swagger"
|
||||||
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
|
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
|
||||||
|
_ "github.com/gogf/gf/contrib/nosql/redis/v2" // common utils.WithLock 需要 go-redis adapter(WATCH/MULTI 归属释放)
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -20,7 +22,8 @@ func main() {
|
|||||||
http.RouteRegister([]interface{}{
|
http.RouteRegister([]interface{}{
|
||||||
knapsackController.Knapsack,
|
knapsackController.Knapsack,
|
||||||
marketController.Market,
|
marketController.Market,
|
||||||
walletController.Wallet,
|
walletController.Account,
|
||||||
|
pricingController.Pricing,
|
||||||
})
|
})
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
knapsackConsts "shop-user-trade/consts/knapsack"
|
knapsackConsts "shop-user-trade/consts/knapsack"
|
||||||
"shop-user-trade/model/config"
|
"shop-user-trade/model/config"
|
||||||
|
|
||||||
"gitea.com/red-future/common/beans"
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package dto
|
|||||||
import (
|
import (
|
||||||
marketConsts "shop-user-trade/consts/market"
|
marketConsts "shop-user-trade/consts/market"
|
||||||
|
|
||||||
"gitea.com/red-future/common/beans"
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/os/gtime"
|
||||||
|
|
||||||
|
pricingConsts "shop-user-trade/consts/pricing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ====================== 建单(不动钱) ======================
|
||||||
|
|
||||||
|
// OpenOrderReq 建单请求(执行开始调用,只建单不动钱),幂等键 subjectType+subjectId+bizOrderNo
|
||||||
|
type OpenOrderReq struct {
|
||||||
|
g.Meta `path:"/open_order" method:"post" tags:"计价扣费" summary:"建单" dc:"执行开始建计费单(不动钱),幂等键 subjectType+subjectId+bizOrderNo"`
|
||||||
|
|
||||||
|
UserId int64 `json:"userId" v:"required|min:1" dc:"用户ID"`
|
||||||
|
SubjectType string `json:"subjectType" v:"required|in:workflow,model,business" dc:"计价对象类型"`
|
||||||
|
SubjectID string `json:"subjectId" v:"required" dc:"计价对象ID"`
|
||||||
|
ChargeMode string `json:"chargeMode" dc:"计费方式:workflow 必填(rules 已配三模式之一);model/business 可空(默认取 unit/per_period)"`
|
||||||
|
BizOrderNo string `json:"bizOrderNo" v:"required" dc:"业务单据号(幂等键,如工作流 execId)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 结算(成功:按最终产出) ======================
|
||||||
|
|
||||||
|
// SettleReq 结算请求(按实际用量计价实收,余额不足允许为负)
|
||||||
|
type SettleReq struct {
|
||||||
|
g.Meta `path:"/settle" method:"post" tags:"计价扣费" summary:"结算" dc:"按实际用量计价实收(允许负余额),幂等"`
|
||||||
|
|
||||||
|
OrderId int64 `json:"orderId" dc:"计费单ID(与 subjectType+subjectId+bizOrderNo 二选一)"`
|
||||||
|
SubjectType string `json:"subjectType" dc:"计价对象类型(orderId 为空时必填)"`
|
||||||
|
SubjectID string `json:"subjectId" dc:"计价对象ID(orderId 为空时必填)"`
|
||||||
|
BizOrderNo string `json:"bizOrderNo" dc:"业务单据号(orderId 为空时必填)"`
|
||||||
|
Usage map[string]any `json:"usage" v:"required" dc:"用量JSON对象(前端传对象)。工作流口径:{durationSec,feeByModel}——feeByModel 各模型=按次调 /calc 已记录费用(per_token 结算/时长取消补收据此合计实收);durationSec=生成视频总时长(per_item/per_second)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 取消(按已消耗用量结算) ======================
|
||||||
|
|
||||||
|
// CancelReq 取消请求(中途取消按已消耗用量计价扣费,非退款)
|
||||||
|
type CancelReq struct {
|
||||||
|
g.Meta `path:"/cancel" method:"post" tags:"计价扣费" summary:"取消" dc:"中途取消按已消耗用量计价实收(非退款),幂等"`
|
||||||
|
|
||||||
|
OrderId int64 `json:"orderId" dc:"计费单ID(与 subjectType+subjectId+bizOrderNo 二选一)"`
|
||||||
|
SubjectType string `json:"subjectType" dc:"计价对象类型(orderId 为空时必填)"`
|
||||||
|
SubjectID string `json:"subjectId" dc:"计价对象ID(orderId 为空时必填)"`
|
||||||
|
BizOrderNo string `json:"bizOrderNo" dc:"业务单据号(orderId 为空时必填)"`
|
||||||
|
Usage map[string]any `json:"usage" v:"required" dc:"已消耗用量JSON对象(前端传对象)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 失败(不扣费) ======================
|
||||||
|
|
||||||
|
// FailReq 失败请求(执行失败/无产出,不扣费)
|
||||||
|
type FailReq struct {
|
||||||
|
g.Meta `path:"/fail" method:"post" tags:"计价扣费" summary:"失败处理" dc:"执行失败/无产出,不扣费,幂等"`
|
||||||
|
|
||||||
|
OrderId int64 `json:"orderId" dc:"计费单ID(与 subjectType+subjectId+bizOrderNo 二选一)"`
|
||||||
|
SubjectType string `json:"subjectType" dc:"计价对象类型(orderId 为空时必填)"`
|
||||||
|
SubjectID string `json:"subjectId" dc:"计价对象ID(orderId 为空时必填)"`
|
||||||
|
BizOrderNo string `json:"bizOrderNo" dc:"业务单据号(orderId 为空时必填)"`
|
||||||
|
Reason string `json:"reason" dc:"失败原因"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 查询 ======================
|
||||||
|
|
||||||
|
// ChargeOrderInfo 计费单信息
|
||||||
|
type ChargeOrderInfo struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
SubjectType string `json:"subjectType"`
|
||||||
|
SubjectID string `json:"subjectId"`
|
||||||
|
BizOrderNo string `json:"bizOrderNo"`
|
||||||
|
UserId int64 `json:"userId"`
|
||||||
|
ChargeMode string `json:"chargeMode"`
|
||||||
|
Status int `json:"status"` // 1已建单 2已结算 3已失败
|
||||||
|
ActualAmount float64 `json:"actualAmount"`
|
||||||
|
Usage map[string]any `json:"usage"`
|
||||||
|
RuleSnapshot map[string]any `json:"ruleSnapshot"`
|
||||||
|
SettleTime string `json:"settleTime"`
|
||||||
|
CreatedAt string `json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrderInfoReq 查询计费单(工作流结算前取 CreatedAt 算时长)
|
||||||
|
type GetOrderInfoReq struct {
|
||||||
|
g.Meta `path:"/order" method:"get" tags:"计价扣费" summary:"查询计费单" dc:"按ID或subjectType+subjectId+bizOrderNo查询计费单(工作流结算前取CreatedAt算时长)"`
|
||||||
|
|
||||||
|
Id int64 `json:"id" dc:"计费单ID(与 subjectType+subjectId+bizOrderNo 二选一)"`
|
||||||
|
SubjectType string `json:"subjectType" dc:"计价对象类型"`
|
||||||
|
SubjectID string `json:"subjectId" dc:"计价对象ID"`
|
||||||
|
BizOrderNo string `json:"bizOrderNo" dc:"业务单据号"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 算费(不建单不扣费,model-gateway 调用) ======================
|
||||||
|
|
||||||
|
// CalcFeeReq 按用量计算费用(不建单不扣费;未配置/未启用计价即报错)
|
||||||
|
type CalcFeeReq struct {
|
||||||
|
g.Meta `path:"/calc" method:"post" tags:"计价扣费" summary:"按用量计算费用" dc:"按计价配置与用量计算费用(不建单不扣费;model-gateway 调用)"`
|
||||||
|
|
||||||
|
SubjectType string `json:"subjectType" v:"required" dc:"计价对象类型"`
|
||||||
|
SubjectID string `json:"subjectId" v:"required" dc:"计价对象ID"`
|
||||||
|
ChargeMode string `json:"chargeMode" dc:"计费方式(model 可空=配置 unit)"`
|
||||||
|
Usage map[string]any `json:"usage" v:"required" dc:"用量JSON对象(ChargeUsage 形状:promptTokens/completionTokens/cachedTokens/mediaType/durationSec 等)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalcFeeRes 算费结果
|
||||||
|
type CalcFeeRes struct {
|
||||||
|
Cost float64 `json:"cost" dc:"费用(元,不足1分向上取整)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== DAO 入参(统一吃 DTO,同 model-gateway) ======================
|
||||||
|
|
||||||
|
// GetChargeOrderReq DAO 查询计费单:Id 与 SubjectType+SubjectID+BizOrderNo 二选一
|
||||||
|
type GetChargeOrderReq struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
SubjectType string `json:"subjectType"`
|
||||||
|
SubjectID string `json:"subjectId"`
|
||||||
|
BizOrderNo string `json:"bizOrderNo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateChargeOrderReq DAO 创建计费单
|
||||||
|
type CreateChargeOrderReq struct {
|
||||||
|
SubjectType pricingConsts.SubjectType `json:"subjectType"`
|
||||||
|
SubjectID string `json:"subjectId"`
|
||||||
|
BizOrderNo string `json:"bizOrderNo"`
|
||||||
|
UserId int64 `json:"userId"`
|
||||||
|
ChargeMode pricingConsts.ChargeMode `json:"chargeMode"`
|
||||||
|
Status pricingConsts.ChargeOrderStatus `json:"status"`
|
||||||
|
RuleSnapshot map[string]any `json:"ruleSnapshot"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SettleChargeOrderReq DAO 结算迁移(CREATED→SETTLED,条件更新幂等)
|
||||||
|
type SettleChargeOrderReq struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
ActualAmount float64 `json:"actualAmount"`
|
||||||
|
Usage map[string]any `json:"usage"`
|
||||||
|
RuleSnapshot map[string]any `json:"ruleSnapshot"`
|
||||||
|
SettleTime *gtime.Time `json:"settleTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FailChargeOrderReq DAO 失败迁移(CREATED→FAILED,条件更新幂等)
|
||||||
|
type FailChargeOrderReq struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ====================== 配置管理 ======================
|
||||||
|
|
||||||
|
// SavePricingConfigReq 新增/更新计价配置请求(费率全 DB 配置,改价/加档只改 rules)
|
||||||
|
type SavePricingConfigReq struct {
|
||||||
|
g.Meta `path:"/config/save" method:"post" tags:"计价配置" summary:"保存计价配置" dc:"新增或更新计价配置(subject_type+subject_id 唯一)"`
|
||||||
|
|
||||||
|
Id int64 `json:"id" dc:"配置ID(>0 更新,0 新增)"`
|
||||||
|
SubjectType string `json:"subjectType" v:"required|in:workflow,model,business" dc:"计价对象类型"`
|
||||||
|
SubjectID string `json:"subjectId" v:"required" dc:"计价对象ID"`
|
||||||
|
Rules map[string]any `json:"rules" v:"required" dc:"费率JSON对象(见设计§4;前端传对象)"`
|
||||||
|
MinBalance *float64 `json:"minBalance" dc:"门禁:调用前可用余额须>=该值(元),0=不校验"`
|
||||||
|
Currency string `json:"currency" dc:"货币类型,默认CNY"`
|
||||||
|
Enabled *int `json:"enabled" dc:"1启用 0停用"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SavePricingConfigRes 保存计价配置数据
|
||||||
|
type SavePricingConfigRes struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPricingConfigReq 查询计价配置请求
|
||||||
|
type GetPricingConfigReq struct {
|
||||||
|
g.Meta `path:"/config/get" method:"get" tags:"计价配置" summary:"查询计价配置" dc:"按 subjectType+subjectId 查询计价配置"`
|
||||||
|
|
||||||
|
SubjectType string `json:"subjectType" v:"required" dc:"计价对象类型"`
|
||||||
|
SubjectID string `json:"subjectId" v:"required" dc:"计价对象ID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPricingConfigRes 计价配置信息
|
||||||
|
type GetPricingConfigRes struct {
|
||||||
|
ID int64 `json:"id,string"`
|
||||||
|
SubjectType string `json:"subjectType" dc:"计价对象类型"`
|
||||||
|
SubjectID string `json:"subjectId" dc:"计价对象ID"`
|
||||||
|
Rules map[string]any `json:"rules" dc:"费率JSON对象(见设计§4)"`
|
||||||
|
MinBalance float64 `json:"minBalance" dc:"门禁:调用前可用余额须>=该值(元),0=不校验"`
|
||||||
|
Currency string `json:"currency" dc:"货币类型,默认CNY"`
|
||||||
|
Enabled int `json:"enabled" dc:"1启用 0停用"`
|
||||||
|
CreatedAt string `json:"createdAt" dc:"创建时间"`
|
||||||
|
UpdatedAt string `json:"updatedAt" dc:"更新时间"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SubjectListReq 计价对象枚举请求
|
||||||
|
type SubjectListReq struct {
|
||||||
|
g.Meta `path:"/subjects" method:"get" tags:"计价配置" summary:"计价对象枚举" dc:"workflow + 实时model-gateway系统模型 + 固定业务模块"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubjectListRes 计价对象枚举响应
|
||||||
|
type SubjectListRes struct {
|
||||||
|
Subjects []SubjectInfo `json:"subjects"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubjectInfo 计价对象枚举项
|
||||||
|
type SubjectInfo struct {
|
||||||
|
SubjectType string `json:"subjectType"`
|
||||||
|
SubjectID string `json:"subjectId"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ModelType string `json:"modelType,omitempty"` // 模型类型名(管理端分组展示,如 推理/视频)
|
||||||
|
ModelTypeCode int `json:"modelTypeCode,omitempty"` // 模型类型编码:100推理/200图片/300音频/600视频
|
||||||
|
ChargeModes []string `json:"chargeModes,omitempty"` // workflow/business 用(可选的计费方式)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChargeModeListReq 按主体查询可选计费方式请求
|
||||||
|
type ChargeModeListReq struct {
|
||||||
|
g.Meta `path:"/subjects/charge-modes" method:"get" tags:"计价配置" summary:"查询主体可选计费方式" dc:"按 subjectType+subjectId 返回该主体可选计费方式(workflow/business;model 无需选择不入参)"`
|
||||||
|
|
||||||
|
SubjectType string `json:"subjectType" v:"required|in:workflow,business" dc:"计价对象类型(model 非法:模型计费方式无需选择)"`
|
||||||
|
SubjectID string `json:"subjectId" v:"required" dc:"计价对象ID:workflow→workflow;business→业务模块标识"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChargeModeListRes 可选计费方式响应
|
||||||
|
type ChargeModeListRes struct {
|
||||||
|
SubjectType string `json:"subjectType" dc:"计价对象类型"`
|
||||||
|
SubjectID string `json:"subjectId" dc:"计价对象ID"`
|
||||||
|
ChargeModes []ChargeModeInfo `json:"chargeModes" dc:"可选计费方式列表"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChargeModeInfo 计费方式项
|
||||||
|
type ChargeModeInfo struct {
|
||||||
|
Mode string `json:"mode" dc:"提交值:per_item/per_second/per_token/per_1K/per_1M/per_1/per_minute/per_hour/per_char/per_period"`
|
||||||
|
Name string `json:"name" dc:"展示名:按条/按秒/按token/每千token/每百万token/每张·每个/每分钟/每小时/每字/周期订阅"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
|
||||||
|
walletConsts "shop-user-trade/consts/wallet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetAccountByUserIdReq 获取钱包请求
|
||||||
|
type GetAccountByUserIdReq struct {
|
||||||
|
g.Meta `path:"/get" method:"get" tags:"钱包管理" summary:"获取钱包" dc:"根据用户ID获取钱包信息"`
|
||||||
|
|
||||||
|
UserId int64 `json:"userId" v:"required|min:1" dc:"用户ID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountInfo 钱包信息
|
||||||
|
type AccountInfo struct {
|
||||||
|
ID int64 `json:"id" dc:"钱包ID"`
|
||||||
|
UserID int64 `json:"userId" dc:"用户ID"`
|
||||||
|
Balance float64 `json:"balance" dc:"可用余额(元),负值=欠费"`
|
||||||
|
Currency string `json:"currency" dc:"货币类型"`
|
||||||
|
Status int `json:"status" dc:"状态:1启用 0禁用 -1冻结"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RechargeReq 充值请求
|
||||||
|
type RechargeReq struct {
|
||||||
|
g.Meta `path:"/recharge" method:"post" tags:"钱包管理" summary:"充值" dc:"为用户钱包充值"`
|
||||||
|
|
||||||
|
UserId int64 `json:"userId" v:"required|min:1" dc:"用户ID"`
|
||||||
|
UserName string `json:"userName" v:"required" dc:"账户名"`
|
||||||
|
Amount float64 `json:"amount" v:"required|min:0.01" dc:"充值金额(元,保留2位小数)"`
|
||||||
|
Currency string `json:"currency" dc:"货币类型,默认CNY"`
|
||||||
|
OrderNo string `json:"orderNo" dc:"外部订单号"`
|
||||||
|
Description string `json:"description" dc:"描述"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountLogsReq 获取钱包流水请求
|
||||||
|
type GetAccountLogsReq struct {
|
||||||
|
g.Meta `path:"/logs" method:"get" tags:"钱包管理" summary:"获取钱包流水" dc:"分页获取钱包账务流水"`
|
||||||
|
|
||||||
|
UserId int64 `json:"userId" v:"required|min:1" dc:"用户ID"`
|
||||||
|
*beans.Page `json:"page"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountLogData 钱包流水数据
|
||||||
|
type AccountLogData struct {
|
||||||
|
Logs []AccountLogInfo `json:"logs"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountLogInfo 钱包流水信息
|
||||||
|
type AccountLogInfo struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
OrderNo string `json:"orderNo" dc:"业务单据号(计费单号/充值单号)"`
|
||||||
|
TransactionNo string `json:"transactionNo" dc:"账务流水号(唯一,幂等)"`
|
||||||
|
Type string `json:"type" dc:"账务类型:income/expense"`
|
||||||
|
Amount float64 `json:"amount" dc:"本次变动金额(元,正数)"`
|
||||||
|
BalanceBefore float64 `json:"balanceBefore" dc:"可用余额变动前(元)"`
|
||||||
|
BalanceAfter float64 `json:"balanceAfter" dc:"可用余额变动后(元)"`
|
||||||
|
Currency string `json:"currency" dc:"货币类型"`
|
||||||
|
Description string `json:"description" dc:"描述"`
|
||||||
|
CreatedAt string `json:"createdAt" dc:"创建时间"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== DAO 入参(统一吃 DTO,同 model-gateway) ======================
|
||||||
|
|
||||||
|
// CreateAccountReq 创建钱包请求(钱包不存在时自动创建,余额 0)
|
||||||
|
type CreateAccountReq struct {
|
||||||
|
UserId int64 `json:"userId"`
|
||||||
|
UserName string `json:"userName"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeAccountReq 单次账务变动请求(事务透明,由调用方在 service 事务内使用)
|
||||||
|
type ChangeAccountReq struct {
|
||||||
|
UserId int64 `json:"userId"`
|
||||||
|
DeltaBalance float64 `json:"deltaBalance"` // 余额增量(元,可为负=扣款)
|
||||||
|
Type walletConsts.WalletLogType `json:"type"` // income/expense
|
||||||
|
Amount float64 `json:"amount"` // 记账金额(元,正数)
|
||||||
|
OrderNo string `json:"orderNo"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ExtraData map[string]interface{} `json:"extraData"`
|
||||||
|
}
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
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"`
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
knapsackConsts "shop-user-trade/consts/knapsack"
|
knapsackConsts "shop-user-trade/consts/knapsack"
|
||||||
"shop-user-trade/model/config"
|
"shop-user-trade/model/config"
|
||||||
|
|
||||||
"gitea.com/red-future/common/beans"
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||||
)
|
)
|
||||||
|
|
||||||
type knapsackCol struct {
|
type knapsackCol struct {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package entity
|
|||||||
import (
|
import (
|
||||||
knapsackConsts "shop-user-trade/consts/knapsack"
|
knapsackConsts "shop-user-trade/consts/knapsack"
|
||||||
|
|
||||||
"gitea.com/red-future/common/beans"
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KnapsackLog 背包操作日志实体
|
// KnapsackLog 背包操作日志实体
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package entity
|
|||||||
import (
|
import (
|
||||||
marketConsts "shop-user-trade/consts/market"
|
marketConsts "shop-user-trade/consts/market"
|
||||||
|
|
||||||
"gitea.com/red-future/common/beans"
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||||
)
|
)
|
||||||
|
|
||||||
type marketCol struct {
|
type marketCol struct {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package entity
|
|||||||
import (
|
import (
|
||||||
marketConsts "shop-user-trade/consts/market"
|
marketConsts "shop-user-trade/consts/market"
|
||||||
|
|
||||||
"gitea.com/red-future/common/beans"
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MarketLog 市场操作日志实体
|
// MarketLog 市场操作日志实体
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
pricingConsts "shop-user-trade/consts/pricing"
|
||||||
|
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||||
|
"github.com/gogf/gf/v2/os/gtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type chargeOrderCol struct {
|
||||||
|
beans.SQLBaseCol
|
||||||
|
SubjectType string
|
||||||
|
SubjectID string
|
||||||
|
BizOrderNo string
|
||||||
|
UserId string
|
||||||
|
ChargeMode string
|
||||||
|
Status string
|
||||||
|
ActualAmount string
|
||||||
|
Usage string
|
||||||
|
RuleSnapshot string
|
||||||
|
SettleTime string
|
||||||
|
}
|
||||||
|
|
||||||
|
var ChargeOrderCol = chargeOrderCol{
|
||||||
|
SQLBaseCol: beans.DefSQLBaseCol,
|
||||||
|
SubjectType: "subject_type",
|
||||||
|
SubjectID: "subject_id",
|
||||||
|
BizOrderNo: "biz_order_no",
|
||||||
|
UserId: "user_id",
|
||||||
|
ChargeMode: "charge_mode",
|
||||||
|
Status: "status",
|
||||||
|
ActualAmount: "actual_amount",
|
||||||
|
Usage: "usage",
|
||||||
|
RuleSnapshot: "rule_snapshot",
|
||||||
|
SettleTime: "settle_time",
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChargeOrder 计费单实体:一次计价调用 = 一张计费单(幂等键 subject_type+subject_id+biz_order_no)
|
||||||
|
// 状态机:CREATED(已建单,不动钱) → SETTLED(已结算) / FAILED(已失败,不扣费)
|
||||||
|
type ChargeOrder struct {
|
||||||
|
beans.SQLBaseDO `orm:",inherit"`
|
||||||
|
|
||||||
|
SubjectType pricingConsts.SubjectType `orm:"subject_type" json:"subjectType" description:"计价对象类型"`
|
||||||
|
SubjectID string `orm:"subject_id" json:"subjectId" description:"计价对象ID"`
|
||||||
|
BizOrderNo string `orm:"biz_order_no" json:"bizOrderNo" description:"业务单据号(幂等键,如工作流 execId)"`
|
||||||
|
UserId int64 `orm:"user_id" json:"userId,string" description:"扣费用户ID"`
|
||||||
|
ChargeMode pricingConsts.ChargeMode `orm:"charge_mode" json:"chargeMode" description:"计费方式(建单快照)"`
|
||||||
|
Status pricingConsts.ChargeOrderStatus `orm:"status" json:"status" description:"状态:1已建单 2已结算 3已失败"`
|
||||||
|
ActualAmount float64 `orm:"actual_amount" json:"actualAmount" description:"实收金额(元,保留2位小数)"`
|
||||||
|
Usage map[string]any `orm:"usage" json:"usage" description:"实际用量JSON对象"`
|
||||||
|
RuleSnapshot map[string]any `orm:"rule_snapshot" json:"ruleSnapshot" description:"结算用费率快照JSON对象(防改价影响在途单)"`
|
||||||
|
SettleTime *gtime.Time `orm:"settle_time" json:"settleTime" description:"结算时间"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
pricingConsts "shop-user-trade/consts/pricing"
|
||||||
|
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pricingConfigCol struct {
|
||||||
|
beans.SQLBaseCol
|
||||||
|
SubjectType string
|
||||||
|
SubjectID string
|
||||||
|
Rules string
|
||||||
|
MinBalance string
|
||||||
|
Currency string
|
||||||
|
Enabled string
|
||||||
|
}
|
||||||
|
|
||||||
|
var PricingConfigCol = pricingConfigCol{
|
||||||
|
SQLBaseCol: beans.DefSQLBaseCol,
|
||||||
|
SubjectType: "subject_type",
|
||||||
|
SubjectID: "subject_id",
|
||||||
|
Rules: "rules",
|
||||||
|
MinBalance: "min_balance",
|
||||||
|
Currency: "currency",
|
||||||
|
Enabled: "enabled",
|
||||||
|
}
|
||||||
|
|
||||||
|
// PricingConfig 计价配置实体(费率全 DB 配置,改价/加档只改 rules,代码不写死金额)
|
||||||
|
type PricingConfig struct {
|
||||||
|
beans.SQLBaseDO `orm:",inherit"`
|
||||||
|
|
||||||
|
SubjectType pricingConsts.SubjectType `orm:"subject_type" json:"subjectType" description:"计价对象类型:workflow/model/business"`
|
||||||
|
SubjectID string `orm:"subject_id" json:"subjectId" description:"计价对象ID(workflow/模型ID/业务模块标识)"`
|
||||||
|
Rules map[string]any `orm:"rules" json:"rules" description:"费率JSON对象(见设计§4,rules 自描述不再有 charge_mode 列)"`
|
||||||
|
MinBalance float64 `orm:"min_balance" json:"minBalance" description:"门禁:调用前可用余额须>=该值(元),0=不校验"`
|
||||||
|
Currency string `orm:"currency" json:"currency" description:"货币类型"`
|
||||||
|
Enabled int `orm:"enabled" json:"enabled" description:"1启用 0停用"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unit 从 rules 中解析模型计费单位(仅 subject_type=model 有效,其余返回空)。
|
||||||
|
// service 层 per_token 结算/建单解析计费方式时使用,避免重复解析 rules。
|
||||||
|
func (e *PricingConfig) Unit() pricingConsts.ChargeMode {
|
||||||
|
if unit, ok := e.Rules["unit"].(string); ok {
|
||||||
|
return pricingConsts.ChargeMode(unit)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -3,34 +3,36 @@ package entity
|
|||||||
import (
|
import (
|
||||||
walletConsts "shop-user-trade/consts/wallet"
|
walletConsts "shop-user-trade/consts/wallet"
|
||||||
|
|
||||||
"gitea.com/red-future/common/beans"
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||||
)
|
)
|
||||||
|
|
||||||
type walletCol struct {
|
type accountCol struct {
|
||||||
beans.SQLBaseCol
|
beans.SQLBaseCol
|
||||||
UserID string
|
UserID string
|
||||||
|
UserName string
|
||||||
Balance string
|
Balance string
|
||||||
Currency string
|
Currency string
|
||||||
Status string
|
Status string
|
||||||
Version string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var WalletCol = walletCol{
|
var AccountCol = accountCol{
|
||||||
SQLBaseCol: beans.DefSQLBaseCol,
|
SQLBaseCol: beans.DefSQLBaseCol,
|
||||||
UserID: "user_id",
|
UserID: "user_id",
|
||||||
|
UserName: "user_name",
|
||||||
Balance: "balance",
|
Balance: "balance",
|
||||||
Currency: "currency",
|
Currency: "currency",
|
||||||
Status: "status",
|
Status: "status",
|
||||||
Version: "version",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wallet 钱包实体
|
// Account 钱包实体
|
||||||
type Wallet struct {
|
// 单余额模型:balance 可用余额(元),可为负(欠费,靠充值归正)。
|
||||||
|
// status 为整钱包状态(1启用/0禁用/-1冻结,风控用)。
|
||||||
|
type Account struct {
|
||||||
beans.SQLBaseDO `orm:",inherit"`
|
beans.SQLBaseDO `orm:",inherit"`
|
||||||
|
|
||||||
UserID int64 `orm:"user_id" json:"userId,string" description:"用户ID"`
|
UserID int64 `orm:"user_id" json:"userId,string" description:"用户ID"`
|
||||||
Balance int64 `orm:"balance" json:"balance" description:"余额(分)"`
|
UserName string `orm:"user_name" json:"userName" description:"用户名"`
|
||||||
|
Balance float64 `orm:"balance" json:"balance" description:"可用余额(元,保留2位小数),可为负=欠费"`
|
||||||
Currency string `orm:"currency" json:"currency" description:"货币类型:CNY-人民币"`
|
Currency string `orm:"currency" json:"currency" description:"货币类型:CNY-人民币"`
|
||||||
Status walletConsts.WalletStatus `orm:"status" json:"status" description:"状态:1启用/0禁用/-1冻结"`
|
Status walletConsts.WalletStatus `orm:"status" json:"status" description:"状态:1启用/0禁用/-1冻结"`
|
||||||
Version int64 `orm:"version" json:"version" description:"乐观锁版本号"`
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
walletConsts "shop-user-trade/consts/wallet"
|
||||||
|
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccountLog 钱包操作日志实体
|
||||||
|
type AccountLog 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:"账务类型:income/expense"`
|
||||||
|
Amount float64 `orm:"amount" json:"amount" description:"本次变动金额(元,保留2位小数,正数)"`
|
||||||
|
BalanceBefore float64 `orm:"balance_before" json:"balanceBefore" description:"可用余额变动前(元)"`
|
||||||
|
BalanceAfter float64 `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)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type accountLogCol 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 AccountLogCol = accountLogCol{
|
||||||
|
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",
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
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)"`
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
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",
|
|
||||||
}
|
|
||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.com/red-future/common/consul"
|
"gitea.redpowerfuture.com/red-future/common/consul"
|
||||||
"gitea.com/red-future/common/http"
|
"gitea.redpowerfuture.com/red-future/common/http"
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
|
||||||
marketConsts "shop-user-trade/consts/market"
|
marketConsts "shop-user-trade/consts/market"
|
||||||
|
|||||||
@@ -0,0 +1,480 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
pricingConsts "shop-user-trade/consts/pricing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ====================== 用量结构 ======================
|
||||||
|
|
||||||
|
// ChargeUsage 实际用量(结算入参,工作流/调用方上报)。
|
||||||
|
// DurationSec 秒;工作流 token 计费按 FeeByModel 合计实收(各模型=调用方按次调 shop /calc 返回费求和,
|
||||||
|
// 每次调用已 ceilFen 到分、媒体/费率按调用时快照)。逐模型 token/媒体明细由调用方保留(ai-agent
|
||||||
|
// node_execution.token_info),本结构解析只取计价字段,未知键原样落库 order.usage 作审计(见 settleOrder)。
|
||||||
|
// 模型计价字段见各计算器注释(spec §5.3)。
|
||||||
|
type ChargeUsage struct {
|
||||||
|
DurationSec float64 `json:"durationSec"` // 时长(秒):per_second 工作流结算、model 单位计价用;per_item 无 durationItems 时回落单条
|
||||||
|
// DurationItems 逐条产出时长明细(秒,per_item 工作流结算用):一条 = 一次视频节点执行产出的一条成片
|
||||||
|
// (ai-agent 按 node_execution 记录收集,各条独立时长)。per_item 逐条各自落档求和(2×4s→2×29);
|
||||||
|
// 为空回落单条 DurationSec(旧客户端 / 直接 /calc 调用,单条数值与旧口径一致)。
|
||||||
|
DurationItems []float64 `json:"durationItems"`
|
||||||
|
// FeeByModel 各模型已消耗费用(= 调用方按次调 shop /calc 返回费的合计,每次调用已 ceilFen 到分、各次媒体/费率
|
||||||
|
// 按调用时快照)。per_item/per_second 取消补收、per_token 结算的 token 部分按此合计实收(不再按聚合 token
|
||||||
|
// 重算,避免丢失「每次调用不足1分按1分」的兜底与逐调用媒体价——聚合重算会把两笔 0.01 合并成 0.01 且拿错媒体价)。
|
||||||
|
FeeByModel map[string]float64 `json:"feeByModel"`
|
||||||
|
// —— 模型计价扩展(spec §5.3)——
|
||||||
|
PromptTokens int64 `json:"promptTokens"` // token 计费用:输入
|
||||||
|
CompletionTokens int64 `json:"completionTokens"` // token 计费用:输出;per_char 模型=输出字数(调用方把字数映射到该字段传输)
|
||||||
|
CachedTokens int64 `json:"cachedTokens"` // token 计费用:缓存命中
|
||||||
|
ImageCount int64 `json:"imageCount"` // per_1 用(图片张数)
|
||||||
|
MediaType string `json:"mediaType"` // text/audio/video/image(输入媒体)
|
||||||
|
Thinking *bool `json:"thinking"` // 推理 思考/非思考
|
||||||
|
OutputAudio *bool `json:"outputAudio"` // 视频 输出有声/无声
|
||||||
|
OutputResolution string `json:"outputResolution"` // 视频/图片 输出分辨率
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 各计费方式费率 JSON 结构(全 DB 配置) ======================
|
||||||
|
|
||||||
|
// per_item 按条:开放档位(上不封顶),超出最大档位按 overflowUnitPrice 线性叠加。
|
||||||
|
// {"tiers":[{"maxSec":15,"price":29},{"maxSec":30,"price":49},{"maxSec":60,"price":89}],"overflowUnitPrice":0.9}
|
||||||
|
type perItemRules struct {
|
||||||
|
Tiers []perItemTier `json:"tiers"`
|
||||||
|
OverflowUnitPrice float64 `json:"overflowUnitPrice"` // 元/秒,超出最大档位后线性计费
|
||||||
|
}
|
||||||
|
|
||||||
|
type perItemTier struct {
|
||||||
|
MaxSec float64 `json:"maxSec"` // 该档位最大时长(秒),open-ended
|
||||||
|
Price float64 `json:"price"` // 该档位价格(元)
|
||||||
|
}
|
||||||
|
|
||||||
|
// per_second 按秒:单位时长单价。
|
||||||
|
// {"unitPrice":0.9}(0.9元/秒)。
|
||||||
|
type perSecondRules struct {
|
||||||
|
UnitPrice float64 `json:"unitPrice"` // 元/秒
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 计算器注册表 ======================
|
||||||
|
|
||||||
|
// calculator 计费方式计算器(新增方式:实现该接口并按 subject_type 注册进对应分表)。
|
||||||
|
// rules 为 rulesJSON 解析后的强类型费率结构;所有金额单位均为元(保留2位小数)。
|
||||||
|
type calculator interface {
|
||||||
|
// validate 解析并校验 rulesJSON(SaveConfig 与结算时调用)
|
||||||
|
validate(rulesJSON string) (interface{}, error)
|
||||||
|
// charge 结算实际金额(元),不足1分向上取整
|
||||||
|
charge(rules interface{}, usage *ChargeUsage) (float64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ceilFen 金额向上取整到分(1分=0.01元),保证结果保留2位小数(不足1分按1分收)。
|
||||||
|
func ceilFen(v float64) float64 {
|
||||||
|
return math.Ceil(v*100) / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
// roundCost 去浮点噪声:乘法累积误差 ~1e-16,四舍五入到 9 位小数后再 ceilFen,
|
||||||
|
// 避免 2.2×0.9=1.9800000000000002 在分位边界被错误进位(1.98 → 1.99)。
|
||||||
|
func roundCost(v float64) float64 {
|
||||||
|
return math.Round(v*1e9) / 1e9
|
||||||
|
}
|
||||||
|
|
||||||
|
// chargeCalculators 计费方式注册表(按 subject_type 分表;代码不写死金额,只挂计算器)
|
||||||
|
var (
|
||||||
|
// workflow 计算器(spec §4.1 三模式容器,per_token 为空标记)
|
||||||
|
workflowCalculators = map[pricingConsts.ChargeMode]calculator{
|
||||||
|
pricingConsts.ChargeModePerItem: perItemCalculator{},
|
||||||
|
pricingConsts.ChargeModePerSecond: perSecondCalculator{},
|
||||||
|
pricingConsts.ChargeModePerToken: perTokenCalculator{},
|
||||||
|
}
|
||||||
|
// model 计算器(spec §4.2 类型自适应)
|
||||||
|
modelCalculators = map[pricingConsts.ChargeMode]calculator{
|
||||||
|
pricingConsts.ChargeModePer1K: modelTokenCalculator{base: 1000},
|
||||||
|
pricingConsts.ChargeModePer1M: modelTokenCalculator{base: 1e6},
|
||||||
|
pricingConsts.ChargeModePer1: modelUnitCalculator{base: 1, usage: usageImageCount},
|
||||||
|
pricingConsts.ChargeModePerSecond: modelUnitCalculator{base: 1, usage: usageDurationSec},
|
||||||
|
pricingConsts.ChargeModePerMinute: modelUnitCalculator{base: 60, usage: usageDurationSec},
|
||||||
|
pricingConsts.ChargeModePerHour: modelUnitCalculator{base: 3600, usage: usageDurationSec},
|
||||||
|
pricingConsts.ChargeModePerChar: modelUnitCalculator{base: 1, usage: usageCompletionTokens}, // 输出字数经 completionTokens 传输
|
||||||
|
}
|
||||||
|
// business 计算器(spec §4.3 周期订阅)
|
||||||
|
businessCalculators = map[pricingConsts.ChargeMode]calculator{
|
||||||
|
pricingConsts.ChargeModePerPeriod: perPeriodCalculator{},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// getCalculator 获取计算器(按 subject_type 分表)
|
||||||
|
func getCalculator(subjectType pricingConsts.SubjectType, mode pricingConsts.ChargeMode) (calculator, error) {
|
||||||
|
calcs := workflowCalculators
|
||||||
|
switch subjectType {
|
||||||
|
case pricingConsts.SubjectTypeModel:
|
||||||
|
calcs = modelCalculators
|
||||||
|
case pricingConsts.SubjectTypeBusiness:
|
||||||
|
calcs = businessCalculators
|
||||||
|
}
|
||||||
|
c, ok := calcs[mode]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("不支持的计费方式: %s", mode)
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// calcCharge 结算实际金额(元)
|
||||||
|
func calcCharge(subjectType pricingConsts.SubjectType, mode pricingConsts.ChargeMode, rulesJSON string, usage *ChargeUsage) (float64, error) {
|
||||||
|
c, err := getCalculator(subjectType, mode)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
rules, err := c.validate(rulesJSON)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return c.charge(rules, usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== per_item 按条 ======================
|
||||||
|
|
||||||
|
type perItemCalculator struct{}
|
||||||
|
|
||||||
|
func (perItemCalculator) validate(rulesJSON string) (interface{}, error) {
|
||||||
|
var rules perItemRules
|
||||||
|
if err := json.Unmarshal([]byte(rulesJSON), &rules); err != nil {
|
||||||
|
return nil, fmt.Errorf("per_item 费率JSON解析失败: %v", err)
|
||||||
|
}
|
||||||
|
if len(rules.Tiers) == 0 {
|
||||||
|
return nil, errors.New("per_item 费率至少需要一个档位 tiers")
|
||||||
|
}
|
||||||
|
return &rules, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (perItemCalculator) charge(rules interface{}, usage *ChargeUsage) (float64, error) {
|
||||||
|
r := rules.(*perItemRules)
|
||||||
|
// 逐条明细(durationItems:多产出,一条=一次视频节点执行产出的一条成片)为空时回落单条 durationSec,
|
||||||
|
// 兼容旧客户端与直接 /calc 调用(单条数值与旧口径完全一致)。
|
||||||
|
items := usage.DurationItems
|
||||||
|
if len(items) == 0 {
|
||||||
|
if usage.DurationSec <= 0 {
|
||||||
|
return 0, nil // 0秒无产出(中途取消/未生成视频):按已消耗=0 计费,不落入第一档位价
|
||||||
|
}
|
||||||
|
items = []float64{usage.DurationSec}
|
||||||
|
}
|
||||||
|
// 每条按时长落档求和(chargePerItem 单条口径与旧 per_item 单条完全一致)
|
||||||
|
var total float64
|
||||||
|
for _, dur := range items {
|
||||||
|
if dur <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
total += chargePerItem(r, dur)
|
||||||
|
}
|
||||||
|
if total <= 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return ceilFen(roundCost(total)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// chargePerItem 单条按时长落档:命中 tier → tier.Price;超出最大档位(上不封顶)→ 最大档价 + 超出秒数×overflow 线性叠加(元)。
|
||||||
|
func chargePerItem(r *perItemRules, dur float64) float64 {
|
||||||
|
last := r.Tiers[len(r.Tiers)-1]
|
||||||
|
for _, t := range r.Tiers {
|
||||||
|
if dur <= t.MaxSec {
|
||||||
|
return t.Price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
overflow := ceilFen((dur - last.MaxSec) * r.OverflowUnitPrice)
|
||||||
|
return last.Price + overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== per_second 按秒 ======================
|
||||||
|
|
||||||
|
type perSecondCalculator struct{}
|
||||||
|
|
||||||
|
func (perSecondCalculator) validate(rulesJSON string) (interface{}, error) {
|
||||||
|
var rules perSecondRules
|
||||||
|
if err := json.Unmarshal([]byte(rulesJSON), &rules); err != nil {
|
||||||
|
return nil, fmt.Errorf("per_second 费率JSON解析失败: %v", err)
|
||||||
|
}
|
||||||
|
if rules.UnitPrice <= 0 {
|
||||||
|
return nil, errors.New("per_second 费率须配置 unitPrice(元/秒)")
|
||||||
|
}
|
||||||
|
return &rules, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (perSecondCalculator) charge(rules interface{}, usage *ChargeUsage) (float64, error) {
|
||||||
|
r := rules.(*perSecondRules)
|
||||||
|
// 按秒向上取整(不满1秒按1秒计),再乘单价(元/秒)
|
||||||
|
return ceilFen(math.Ceil(usage.DurationSec) * r.UnitPrice), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== per_token 按token ======================
|
||||||
|
|
||||||
|
type perTokenCalculator struct{}
|
||||||
|
|
||||||
|
func (perTokenCalculator) validate(rulesJSON string) (interface{}, error) {
|
||||||
|
// per_token 为 {} 仅启用标记;费率不落槽位(建单无快照),结算按调用方上报的按次费用(FeeByModel)合计实收(spec §5.4)
|
||||||
|
var container map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(rulesJSON), &container); err != nil {
|
||||||
|
return nil, fmt.Errorf("per_token 费率JSON解析失败: %v", err)
|
||||||
|
}
|
||||||
|
return &container, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (perTokenCalculator) charge(rules interface{}, usage *ChargeUsage) (float64, error) {
|
||||||
|
// 不在此计价:per_token 无费率档位(建单槽位 {}),按调用方上报的按次已记录费用(FeeByModel)合计实收,
|
||||||
|
// 由 service 层 sumRecordedFee 处理
|
||||||
|
return 0, errors.New("per_token 计价在 service 层按调用方上报的按次费用(FeeByModel)合计实收")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 模型计费 JSON 结构(model subject rules,spec §4.2) ======================
|
||||||
|
|
||||||
|
// modelRules 模型计费规则:unit + tiered + rules(rules 按序首条命中)
|
||||||
|
type modelRules struct {
|
||||||
|
Unit pricingConsts.ChargeMode `json:"unit"`
|
||||||
|
Tiered bool `json:"tiered"`
|
||||||
|
Rules []modelRule `json:"rules"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type modelRule struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Match *modelMatch `json:"match,omitempty"` // 空=任意调用
|
||||||
|
Price *modelPrice `json:"price"` // 默认价:输入不含以下媒体
|
||||||
|
MediaPrices map[string]*modelPrice `json:"mediaPrices,omitempty"` // 媒体类型→该媒体价(输入含该媒体时)
|
||||||
|
}
|
||||||
|
|
||||||
|
// modelMatch 命中条件(全可选,命中=全部满足)
|
||||||
|
type modelMatch struct {
|
||||||
|
Thinking *bool `json:"thinking,omitempty"` // 思考/非思考
|
||||||
|
OutputAudio *bool `json:"outputAudio,omitempty"` // 输出有声/无声
|
||||||
|
OutputResolution string `json:"outputResolution,omitempty"` // 输出分辨率
|
||||||
|
InputLengthMin int64 `json:"inputLengthMin,omitempty"` // 输入token下界
|
||||||
|
InputLengthMax int64 `json:"inputLengthMax,omitempty"` // 输入token上界
|
||||||
|
}
|
||||||
|
|
||||||
|
// modelPrice 计价项:token 单位用 input/output/cacheHit;非 token 单位用 unitPrice
|
||||||
|
type modelPrice struct {
|
||||||
|
Input float64 `json:"input,omitempty"`
|
||||||
|
Output float64 `json:"output,omitempty"`
|
||||||
|
CacheHit float64 `json:"cacheHit,omitempty"`
|
||||||
|
UnitPrice float64 `json:"unitPrice,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// perPeriodRules 周期订阅规则(business subject,spec §4.3)
|
||||||
|
type perPeriodRules struct {
|
||||||
|
Period string `json:"period"` // year(扩月/季只加 PeriodSet)
|
||||||
|
Price float64 `json:"price"` // 每周期价格(元)
|
||||||
|
}
|
||||||
|
|
||||||
|
// modelRuleMatch 单条 match 判定(nil match=无条件命中)
|
||||||
|
func modelRuleMatch(m *modelMatch, u *ChargeUsage) bool {
|
||||||
|
if m == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if m.Thinking != nil && (u.Thinking == nil || *m.Thinking != *u.Thinking) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if m.OutputAudio != nil && (u.OutputAudio == nil || *m.OutputAudio != *u.OutputAudio) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if m.OutputResolution != "" && m.OutputResolution != u.OutputResolution {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if m.InputLengthMin > 0 && u.PromptTokens < m.InputLengthMin {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if m.InputLengthMax > 0 && u.PromptTokens > m.InputLengthMax {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateMediaPrices mediaPrices 校验:键须 ∈ ModelMediaSet,值须配置 price(token/unit 计算器共用)
|
||||||
|
func validateMediaPrices(mp map[string]*modelPrice) error {
|
||||||
|
for mt, p := range mp {
|
||||||
|
if _, ok := pricingConsts.ModelMediaSet[mt]; !ok {
|
||||||
|
return errors.New("model 费率 mediaPrices 键须为 text/audio/video/image")
|
||||||
|
}
|
||||||
|
if p == nil {
|
||||||
|
return errors.New("model 费率 mediaPrices 值须配置 price")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchModelRule 按序返回首条命中规则(先写优先级高)
|
||||||
|
func matchModelRule(r *modelRules, u *ChargeUsage) *modelRule {
|
||||||
|
for i := range r.Rules {
|
||||||
|
if modelRuleMatch(r.Rules[i].Match, u) {
|
||||||
|
return &r.Rules[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pickModelPrice 命中规则后按输入媒体取价:mediaPrices 命中使用媒体价,否则默认 price。
|
||||||
|
func pickModelPrice(rule *modelRule, usage *ChargeUsage) *modelPrice {
|
||||||
|
if len(rule.MediaPrices) > 0 {
|
||||||
|
if p, ok := rule.MediaPrices[usage.MediaType]; ok {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rule.Price
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== model token 计算器(per_1K / per_1M) ======================
|
||||||
|
|
||||||
|
// modelTokenCalculator 模型 token 计价:首条命中规则后按基准换算。
|
||||||
|
// cost = (prompt-cached)/base×input + cached/base×cacheHit + completion/base×output
|
||||||
|
type modelTokenCalculator struct{ base float64 }
|
||||||
|
|
||||||
|
func (c modelTokenCalculator) validate(rulesJSON string) (interface{}, error) {
|
||||||
|
var r modelRules
|
||||||
|
if err := json.Unmarshal([]byte(rulesJSON), &r); err != nil {
|
||||||
|
return nil, fmt.Errorf("model token 费率JSON解析失败: %v", err)
|
||||||
|
}
|
||||||
|
if r.Unit != pricingConsts.ChargeModePer1K && r.Unit != pricingConsts.ChargeModePer1M {
|
||||||
|
return nil, errors.New("model token 费率 unit 须为 per_1K/per_1M")
|
||||||
|
}
|
||||||
|
// 实例基准与 JSON unit 交叉校验:per_1K 实例 base=1000,per_1M 实例 base=1e6,
|
||||||
|
// 防止 per_1M 配置配 per_1K 单位导致结算 1000 倍价差(P2)。
|
||||||
|
if (c.base == 1000) != (r.Unit == pricingConsts.ChargeModePer1K) {
|
||||||
|
return nil, errors.New("model token 费率 unit 与实例基准不匹配")
|
||||||
|
}
|
||||||
|
if len(r.Rules) == 0 {
|
||||||
|
return nil, errors.New("model 费率至少一条 rules")
|
||||||
|
}
|
||||||
|
for i := range r.Rules {
|
||||||
|
if r.Rules[i].Price == nil {
|
||||||
|
return nil, errors.New("model 费率规则须配置 price")
|
||||||
|
}
|
||||||
|
if err := validateMediaPrices(r.Rules[i].MediaPrices); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.Tiered {
|
||||||
|
if err := validateTieredBands(r.Rules); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateTieredBands 阶梯分档校验:带长度区间的规则按下界升序后相邻不重叠。
|
||||||
|
// 无长度区间规则不参与;上不封顶档(max=0)须为最后一个长度档。
|
||||||
|
func validateTieredBands(rules []modelRule) error {
|
||||||
|
var bands []modelRule
|
||||||
|
for _, r := range rules {
|
||||||
|
if r.Match != nil && (r.Match.InputLengthMin > 0 || r.Match.InputLengthMax > 0) {
|
||||||
|
bands = append(bands, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Slice(bands, func(i, j int) bool {
|
||||||
|
return bands[i].Match.InputLengthMin < bands[j].Match.InputLengthMin
|
||||||
|
})
|
||||||
|
var prevMax int64 = -1
|
||||||
|
openEnded := false
|
||||||
|
for _, r := range bands {
|
||||||
|
m := r.Match
|
||||||
|
if openEnded {
|
||||||
|
return errors.New("阶梯档位区间重叠:上不封顶档后不能再有档位")
|
||||||
|
}
|
||||||
|
if m.InputLengthMin <= prevMax {
|
||||||
|
return errors.New("阶梯档位区间重叠:规则间 InputLengthMin 须 > 前一档 InputLengthMax")
|
||||||
|
}
|
||||||
|
if m.InputLengthMax == 0 {
|
||||||
|
openEnded = true // 上不封顶,之后不可再有长度档
|
||||||
|
} else {
|
||||||
|
prevMax = m.InputLengthMax
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c modelTokenCalculator) charge(rules interface{}, usage *ChargeUsage) (float64, error) {
|
||||||
|
r := rules.(*modelRules)
|
||||||
|
rule := matchModelRule(r, usage)
|
||||||
|
if rule == nil {
|
||||||
|
return 0, errors.New("无匹配计费规则")
|
||||||
|
}
|
||||||
|
p := pickModelPrice(rule, usage)
|
||||||
|
// 防负成本:异常/边界用量报 cached > prompt 时钳为 0,避免结算出现负数(M3)。
|
||||||
|
promptInput := usage.PromptTokens - usage.CachedTokens
|
||||||
|
if promptInput < 0 {
|
||||||
|
promptInput = 0
|
||||||
|
}
|
||||||
|
cost := float64(promptInput)/c.base*p.Input +
|
||||||
|
float64(usage.CachedTokens)/c.base*p.CacheHit +
|
||||||
|
float64(usage.CompletionTokens)/c.base*p.Output
|
||||||
|
return ceilFen(roundCost(cost)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== model 单位计算器(per_1 / per_second / per_minute / per_hour / per_char) ======================
|
||||||
|
|
||||||
|
// modelUnitCalculator 模型非 token 计价:用量/base×unitPrice。
|
||||||
|
// usage 为用量提取函数(张数/秒/秒/秒/字数),base 为基准(1/1/60/3600/1)。
|
||||||
|
type modelUnitCalculator struct {
|
||||||
|
base float64
|
||||||
|
usage func(*ChargeUsage) float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c modelUnitCalculator) validate(rulesJSON string) (interface{}, error) {
|
||||||
|
var r modelRules
|
||||||
|
if err := json.Unmarshal([]byte(rulesJSON), &r); err != nil {
|
||||||
|
return nil, fmt.Errorf("model 单位费率JSON解析失败: %v", err)
|
||||||
|
}
|
||||||
|
if !pricingConsts.ModelUnitSet[r.Unit] {
|
||||||
|
return nil, errors.New("model 单位费率 unit 不在允许集合内")
|
||||||
|
}
|
||||||
|
if len(r.Rules) == 0 {
|
||||||
|
return nil, errors.New("model 费率至少一条 rules")
|
||||||
|
}
|
||||||
|
for i := range r.Rules {
|
||||||
|
if r.Rules[i].Price == nil {
|
||||||
|
return nil, errors.New("model 费率规则须配置 price")
|
||||||
|
}
|
||||||
|
if err := validateMediaPrices(r.Rules[i].MediaPrices); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c modelUnitCalculator) charge(rules interface{}, usage *ChargeUsage) (float64, error) {
|
||||||
|
r := rules.(*modelRules)
|
||||||
|
rule := matchModelRule(r, usage)
|
||||||
|
if rule == nil {
|
||||||
|
return 0, errors.New("无匹配计费规则")
|
||||||
|
}
|
||||||
|
p := pickModelPrice(rule, usage)
|
||||||
|
return ceilFen(roundCost(c.usage(usage) / c.base * p.UnitPrice)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用量提取函数
|
||||||
|
func usageDurationSec(u *ChargeUsage) float64 { return u.DurationSec }
|
||||||
|
|
||||||
|
// usageCompletionTokens per_char 用:调用方把输出字数映射到 CompletionTokens 传输(无独立 charCount 字段)
|
||||||
|
func usageCompletionTokens(u *ChargeUsage) float64 { return float64(u.CompletionTokens) }
|
||||||
|
func usageImageCount(u *ChargeUsage) float64 { return float64(u.ImageCount) }
|
||||||
|
|
||||||
|
// ====================== per_period 周期订阅(business) ======================
|
||||||
|
|
||||||
|
type perPeriodCalculator struct{}
|
||||||
|
|
||||||
|
func (perPeriodCalculator) validate(rulesJSON string) (interface{}, error) {
|
||||||
|
var r perPeriodRules
|
||||||
|
if err := json.Unmarshal([]byte(rulesJSON), &r); err != nil {
|
||||||
|
return nil, fmt.Errorf("per_period 费率JSON解析失败: %v", err)
|
||||||
|
}
|
||||||
|
if !pricingConsts.PeriodSet[r.Period] {
|
||||||
|
return nil, fmt.Errorf("不支持的周期: %s", r.Period)
|
||||||
|
}
|
||||||
|
if r.Price <= 0 {
|
||||||
|
return nil, errors.New("per_period 费率须配置 price>0")
|
||||||
|
}
|
||||||
|
return &r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (perPeriodCalculator) charge(rules interface{}, usage *ChargeUsage) (float64, error) {
|
||||||
|
r := rules.(*perPeriodRules)
|
||||||
|
return ceilFen(r.Price), nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
|
||||||
|
pricingConsts "shop-user-trade/consts/pricing"
|
||||||
|
chargeDao "shop-user-trade/dao/pricing"
|
||||||
|
pricingDto "shop-user-trade/model/dto/pricing"
|
||||||
|
pricingEntity "shop-user-trade/model/entity/pricing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ====================== 配置管理 ======================
|
||||||
|
|
||||||
|
// SavePricingConfig 新增/更新计价配置(费率全 DB 配置;按 subject 类型校验 rules 结构,防止脏配置上线后结算报错)
|
||||||
|
func (s *pricing) SavePricingConfig(ctx context.Context, req *pricingDto.SavePricingConfigReq) (*pricingDto.SavePricingConfigRes, error) {
|
||||||
|
subjectType := pricingConsts.SubjectType(req.SubjectType)
|
||||||
|
rulesBytes, marshalErr := json.Marshal(req.Rules)
|
||||||
|
if marshalErr != nil {
|
||||||
|
return nil, fmt.Errorf("费率JSON序列化失败: %v", marshalErr)
|
||||||
|
}
|
||||||
|
if err := s.validateConfigRules(subjectType, string(rulesBytes)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if req.Currency == "" {
|
||||||
|
req.Currency = "CNY"
|
||||||
|
}
|
||||||
|
if req.Id == 0 && g.IsEmpty(req.Enabled) {
|
||||||
|
req.Enabled = new(1)
|
||||||
|
}
|
||||||
|
var id int64
|
||||||
|
var err error
|
||||||
|
if req.Id > 0 {
|
||||||
|
// Update 返回受影响行数;配置 ID 即 req.Id
|
||||||
|
if _, err = chargeDao.PricingConfig.Update(ctx, req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
id = req.Id
|
||||||
|
} else {
|
||||||
|
id, err = chargeDao.PricingConfig.Insert(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &pricingDto.SavePricingConfigRes{Id: id}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPricingConfig 查询计价配置
|
||||||
|
func (s *pricing) GetPricingConfig(ctx context.Context, req *pricingDto.GetPricingConfigReq) (res *pricingDto.GetPricingConfigRes, err error) {
|
||||||
|
cfg, err := chargeDao.PricingConfig.Get(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cfg == nil {
|
||||||
|
return nil, ErrConfigNotFound
|
||||||
|
}
|
||||||
|
res = new(pricingDto.GetPricingConfigRes)
|
||||||
|
if err = gconv.Struct(cfg, res); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateConfigRules 按 subject 类型校验 rules JSON(spec §4):
|
||||||
|
//
|
||||||
|
// workflow:三模式容器逐个校验已配模式;model:先解析 unit 再用对应计算器;business:per_period 计算器
|
||||||
|
func (s *pricing) validateConfigRules(subjectType pricingConsts.SubjectType, rulesJSON string) error {
|
||||||
|
switch subjectType {
|
||||||
|
case pricingConsts.SubjectTypeWorkflow:
|
||||||
|
return s.validateWorkflowRules(rulesJSON)
|
||||||
|
case pricingConsts.SubjectTypeModel:
|
||||||
|
var cfg struct {
|
||||||
|
Unit pricingConsts.ChargeMode `json:"unit"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(rulesJSON), &cfg); err != nil {
|
||||||
|
return fmt.Errorf("model 费率JSON解析失败: %v", err)
|
||||||
|
}
|
||||||
|
c, err := getCalculator(subjectType, cfg.Unit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = c.validate(rulesJSON)
|
||||||
|
return err
|
||||||
|
case pricingConsts.SubjectTypeBusiness:
|
||||||
|
c, err := getCalculator(subjectType, pricingConsts.ChargeModePerPeriod)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = c.validate(rulesJSON)
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return errors.New("未知计价对象类型: " + string(subjectType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateWorkflowRules 校验三模式容器:逐个校验已配模式(spec §4.1)
|
||||||
|
func (s *pricing) validateWorkflowRules(rulesJSON string) error {
|
||||||
|
var container map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal([]byte(rulesJSON), &container); err != nil {
|
||||||
|
return fmt.Errorf("workflow 费率JSON解析失败: %v", err)
|
||||||
|
}
|
||||||
|
if len(container) == 0 {
|
||||||
|
return errors.New("workflow 费率至少配置一种模式")
|
||||||
|
}
|
||||||
|
for mode, raw := range container {
|
||||||
|
chargeMode := pricingConsts.ChargeMode(mode)
|
||||||
|
c, err := getCalculator(pricingConsts.SubjectTypeWorkflow, chargeMode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = c.validate(string(raw)); err != nil {
|
||||||
|
return fmt.Errorf("workflow %s 模式校验失败: %v", mode, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEnabledConfig 获取启用中的计价配置
|
||||||
|
func (s *pricing) getEnabledConfig(ctx context.Context, subjectType pricingConsts.SubjectType, subjectID string) (*pricingEntity.PricingConfig, error) {
|
||||||
|
cfg, err := chargeDao.PricingConfig.Get(ctx, &pricingDto.GetPricingConfigReq{SubjectType: string(subjectType), SubjectID: subjectID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cfg == nil {
|
||||||
|
return nil, ErrConfigNotFound
|
||||||
|
}
|
||||||
|
if cfg.Enabled != 1 {
|
||||||
|
return nil, errors.New("计价配置未启用: " + subjectID)
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveChargeMode 建单时解析实际计费方式与规则快照(spec §6 OpenOrder 校验):
|
||||||
|
//
|
||||||
|
// workflow:chargeMode 必填 ∈ rules 已配模式;快照 = rules[chargeMode]
|
||||||
|
// model:chargeMode 可空 = config.unit;快照 = 整个 modelCfg
|
||||||
|
// business:chargeMode 可空 = per_period;快照 = 周期规则
|
||||||
|
func (s *pricing) resolveChargeMode(ctx context.Context, cfg *pricingEntity.PricingConfig, req *pricingDto.OpenOrderReq) (pricingConsts.ChargeMode, map[string]any, error) {
|
||||||
|
switch cfg.SubjectType {
|
||||||
|
case pricingConsts.SubjectTypeWorkflow:
|
||||||
|
if req.ChargeMode == "" {
|
||||||
|
return "", nil, errors.New("workflow 建单须指定 chargeMode")
|
||||||
|
}
|
||||||
|
return pricingConsts.ChargeMode(req.ChargeMode), s.workflowRuleSnapshot(cfg.Rules, req.ChargeMode), nil
|
||||||
|
case pricingConsts.SubjectTypeModel:
|
||||||
|
// model 计费方式固定取配置 unit(cfg.Unit() 见 entity);快照 = 整个 rules 对象
|
||||||
|
return cfg.Unit(), cfg.Rules, nil
|
||||||
|
case pricingConsts.SubjectTypeBusiness:
|
||||||
|
return pricingConsts.ChargeModePerPeriod, cfg.Rules, nil
|
||||||
|
default:
|
||||||
|
return "", nil, errors.New("未知计价对象类型: " + string(cfg.SubjectType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// workflowRuleSnapshot 从三模式容器取选中模式的规则片段对象(spec §4.1)
|
||||||
|
func (s *pricing) workflowRuleSnapshot(rules map[string]any, mode string) map[string]any {
|
||||||
|
if snapshot, ok := rules[mode].(map[string]any); ok {
|
||||||
|
return snapshot
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,426 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
|
"github.com/gogf/gf/v2/os/gtime"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||||||
|
|
||||||
|
pricingConsts "shop-user-trade/consts/pricing"
|
||||||
|
"shop-user-trade/consts/public"
|
||||||
|
walletConsts "shop-user-trade/consts/wallet"
|
||||||
|
chargeDao "shop-user-trade/dao/pricing"
|
||||||
|
walletDao "shop-user-trade/dao/wallet"
|
||||||
|
pricingDto "shop-user-trade/model/dto/pricing"
|
||||||
|
walletDto "shop-user-trade/model/dto/wallet"
|
||||||
|
pricingEntity "shop-user-trade/model/entity/pricing"
|
||||||
|
walletService "shop-user-trade/service/wallet"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrOrderAlreadyHandled 计费单已被处理(并发幂等:让事务回滚,避免账务重复)
|
||||||
|
ErrOrderAlreadyHandled = errors.New("计费单已被处理")
|
||||||
|
// ErrConfigNotFound 计价配置不存在(service 层业务错误)
|
||||||
|
ErrConfigNotFound = errors.New("计价配置不存在")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pricing pricing 计价服务:算钱(定价+计费单生命周期+门禁)。
|
||||||
|
// 职责:建单(不动钱)→ 结算/取消(实收)→ 失败(不扣费)。
|
||||||
|
// 锁经 wallet service(common 统一锁),账务经钱包 dao 的 Change 原语;
|
||||||
|
// 钱包与计费单状态在同一事务内原子更新,不直接操作钱包表。
|
||||||
|
var Pricing = new(pricing)
|
||||||
|
|
||||||
|
type pricing struct{}
|
||||||
|
|
||||||
|
// ====================== 建单(不动钱) ======================
|
||||||
|
|
||||||
|
// OpenOrder 建单:校验配置 + 解析计费方式 + 门禁(余额 >= min_balance,非冻结),建 CREATED 计费单,不动钱。
|
||||||
|
// 幂等键 subject_type + subject_id + biz_order_no(工作流 execId),重复调用返回既有单。
|
||||||
|
func (s *pricing) OpenOrder(ctx context.Context, req *pricingDto.OpenOrderReq) (*pricingDto.ChargeOrderInfo, error) {
|
||||||
|
cfg, err := s.getEnabledConfig(ctx, pricingConsts.SubjectType(req.SubjectType), req.SubjectID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 幂等快路径:同 subject + biz_order_no 已建单直接返回
|
||||||
|
if exist, err := chargeDao.ChargeOrder.GetOrder(ctx, &pricingDto.GetChargeOrderReq{
|
||||||
|
SubjectType: req.SubjectType, SubjectID: req.SubjectID, BizOrderNo: req.BizOrderNo,
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if exist != nil {
|
||||||
|
info := s.toOrderInfo(exist)
|
||||||
|
return &info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析计费方式与规则快照(含 chargeMode 校验)
|
||||||
|
chargeMode, ruleSnapshot, err := s.resolveChargeMode(ctx, cfg, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建单前校验费率 JSON(脏配置在建单即暴露,不拖到结算)
|
||||||
|
c, err := getCalculator(cfg.SubjectType, chargeMode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ruleJSON, err := json.Marshal(ruleSnapshot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("费率快照序列化失败: %v", err)
|
||||||
|
}
|
||||||
|
if _, err = c.validate(string(ruleJSON)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 门禁(非冻结):调用前可用余额须 >= min_balance。
|
||||||
|
// 不动钱,普通读即可,无需锁钱包;真正的扣费发生在结算时(允许负余额兜底)。
|
||||||
|
w, err := walletDao.Account.Get(ctx, &walletDto.GetAccountByUserIdReq{UserId: req.UserId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if w == nil {
|
||||||
|
return nil, walletService.ErrWalletNotFound
|
||||||
|
}
|
||||||
|
if w.Status != walletConsts.WalletStatusEnabled {
|
||||||
|
return nil, walletService.ErrWalletDisabled
|
||||||
|
}
|
||||||
|
if cfg.MinBalance > 0 && w.Balance < cfg.MinBalance {
|
||||||
|
return nil, fmt.Errorf("余额不足:可用余额须不低于 %.2f 元才能发起", cfg.MinBalance)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := chargeDao.ChargeOrder.Insert(ctx, &pricingDto.CreateChargeOrderReq{
|
||||||
|
SubjectType: cfg.SubjectType,
|
||||||
|
SubjectID: cfg.SubjectID,
|
||||||
|
BizOrderNo: req.BizOrderNo,
|
||||||
|
UserId: req.UserId,
|
||||||
|
ChargeMode: chargeMode,
|
||||||
|
Status: pricingConsts.ChargeOrderStatusCreated,
|
||||||
|
RuleSnapshot: ruleSnapshot, // 费率快照,结算据此计价,防改价影响在途单
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// 唯一键冲突:并发已建单,幂等返回既有单
|
||||||
|
if exist, e2 := chargeDao.ChargeOrder.GetOrder(ctx, &pricingDto.GetChargeOrderReq{
|
||||||
|
SubjectType: req.SubjectType, SubjectID: req.SubjectID, BizOrderNo: req.BizOrderNo,
|
||||||
|
}); e2 == nil && exist != nil {
|
||||||
|
info := s.toOrderInfo(exist)
|
||||||
|
return &info, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entity := &pricingEntity.ChargeOrder{
|
||||||
|
SubjectType: cfg.SubjectType,
|
||||||
|
SubjectID: cfg.SubjectID,
|
||||||
|
BizOrderNo: req.BizOrderNo,
|
||||||
|
UserId: req.UserId,
|
||||||
|
ChargeMode: chargeMode,
|
||||||
|
Status: pricingConsts.ChargeOrderStatusCreated,
|
||||||
|
RuleSnapshot: ruleSnapshot,
|
||||||
|
}
|
||||||
|
entity.Id = id
|
||||||
|
info := s.toOrderInfo(entity)
|
||||||
|
return &info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 结算(成功:按最终产出;取消:按已消耗) ======================
|
||||||
|
|
||||||
|
// Settle 结算:按实际用量计价实收(余额不足允许为负=欠费),幂等。
|
||||||
|
func (s *pricing) Settle(ctx context.Context, req *pricingDto.SettleReq) (*pricingDto.ChargeOrderInfo, error) {
|
||||||
|
order, err := s.resolveOrder(ctx, req.OrderId, req.SubjectType, req.SubjectID, req.BizOrderNo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err = s.settleOrder(ctx, order, req.Usage, "结算:"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
info := s.toOrderInfo(order)
|
||||||
|
return &info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel 取消:中途取消按已消耗用量计价实收(非退款),幂等。
|
||||||
|
func (s *pricing) Cancel(ctx context.Context, req *pricingDto.CancelReq) (*pricingDto.ChargeOrderInfo, error) {
|
||||||
|
order, err := s.resolveOrder(ctx, req.OrderId, req.SubjectType, req.SubjectID, req.BizOrderNo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err = s.settleOrder(ctx, order, req.Usage, "取消结算(已消耗):"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
info := s.toOrderInfo(order)
|
||||||
|
return &info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// settleOrder 核心结算:按 rule_snapshot 计价实收(允许负余额),状态 CREATED → SETTLED。
|
||||||
|
// 钱包写入在独立 DB group,无法随 default 事务回滚;故持用户锁内权威重读状态为 CREATED 才扣费,
|
||||||
|
// 条件状态迁移(CAS)作最后防线。重复结算返回既有结果。
|
||||||
|
func (s *pricing) settleOrder(ctx context.Context, order *pricingEntity.ChargeOrder, usage map[string]any, desc string) (float64, error) {
|
||||||
|
// 快路径(非权威):锁外状态仅供提前返回,扣费与否以锁内重读为准
|
||||||
|
if order.Status == pricingConsts.ChargeOrderStatusSettled {
|
||||||
|
return order.ActualAmount, nil
|
||||||
|
}
|
||||||
|
if order.Status != pricingConsts.ChargeOrderStatusCreated {
|
||||||
|
return 0, errors.New("计费单状态不可结算(已失败或状态异常)")
|
||||||
|
}
|
||||||
|
|
||||||
|
var usageStruct ChargeUsage
|
||||||
|
if err := gconv.Struct(usage, &usageStruct); err != nil {
|
||||||
|
return 0, fmt.Errorf("用量解析失败: %v", err)
|
||||||
|
}
|
||||||
|
// 用建单时费率快照计价(防改价影响在途单);per_token 无快照(槽位 {}),按调用方上报的
|
||||||
|
// 「按次已记录费用」(feeByModel)合计实收——每次模型调用在发生时已由 shop /calc 计价
|
||||||
|
// (calculator.charge 内部 ceilFen,「不足1分按1分」按调用次生效 + 调用时媒体/费率快照),
|
||||||
|
// 直接合计即实收,不再按聚合 token 重算(两笔各 0.01 合并重算成 0.01 的误收根因)。
|
||||||
|
// 调用方原始上报的用量整份落库 order.usage 作审计(本处 gconv 解析只取计价字段,未知键忽略,不影响落库)。
|
||||||
|
var actualAmount float64
|
||||||
|
var err error
|
||||||
|
if order.SubjectType == pricingConsts.SubjectTypeWorkflow && order.ChargeMode == pricingConsts.ChargeModePerToken {
|
||||||
|
actualAmount = sumRecordedFee(usageStruct.FeeByModel)
|
||||||
|
} else {
|
||||||
|
var ruleJSON []byte
|
||||||
|
ruleJSON, err = json.Marshal(order.RuleSnapshot)
|
||||||
|
if err == nil {
|
||||||
|
actualAmount, err = calcCharge(order.SubjectType, order.ChargeMode, string(ruleJSON), &usageStruct)
|
||||||
|
}
|
||||||
|
// per_item / per_second 取消补收:中途取消通常无视频时长产出(时长计价=0),但已完成节点可能已消耗
|
||||||
|
// token。调用方在 cancel 场景附 feeByModel(不含视频节点,其消耗由时长计价覆盖),与时长计价相加;
|
||||||
|
// 正常结算调用方不附费用拆分 → 仅时长,行为不变。
|
||||||
|
if err == nil && order.SubjectType == pricingConsts.SubjectTypeWorkflow && len(usageStruct.FeeByModel) > 0 {
|
||||||
|
actualAmount = ceilFen(roundCost(actualAmount + sumRecordedFee(usageStruct.FeeByModel)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var alreadySettled bool
|
||||||
|
var settledAmount float64
|
||||||
|
err = walletService.Account.WithUserLock(ctx, order.UserId, func(ctx context.Context) error {
|
||||||
|
// 锁内权威重读:并发 Settle/Cancel 经同一用户锁串行,锁内状态才是最终判定。
|
||||||
|
// 钱包 account 与计费单同处 wallet DB group,事务须开在 wallet 组(gfdb.DB(ctx) 无参 = default 组,
|
||||||
|
// 而 shop-user-trade 的 default 组指向 shop_user_trade 库、无钱包表,会在 BEGIN 即失败),
|
||||||
|
// 钱包扣款与订单状态迁移才能在同一事务内原子生效。
|
||||||
|
fresh, err := chargeDao.ChargeOrder.GetOrder(ctx, &pricingDto.GetChargeOrderReq{Id: order.Id})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fresh == nil {
|
||||||
|
return errors.New("计费单不存在")
|
||||||
|
}
|
||||||
|
if fresh.Status == pricingConsts.ChargeOrderStatusSettled {
|
||||||
|
alreadySettled = true
|
||||||
|
settledAmount = fresh.ActualAmount
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if fresh.Status != pricingConsts.ChargeOrderStatusCreated {
|
||||||
|
return errors.New("计费单状态不可结算(已失败或状态异常)")
|
||||||
|
}
|
||||||
|
return gfdb.DB(ctx, public.DbNameWallet).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||||
|
// 实收扣款(允许负余额=欠费,靠充值归正,见技术设计 §9)。
|
||||||
|
if err := walletDao.Account.Change(ctx, &walletDto.ChangeAccountReq{
|
||||||
|
UserId: order.UserId,
|
||||||
|
DeltaBalance: -actualAmount,
|
||||||
|
Type: walletConsts.WalletLogTypeExpense,
|
||||||
|
Amount: actualAmount,
|
||||||
|
OrderNo: fmt.Sprintf("charge:%d", order.Id),
|
||||||
|
Description: desc + string(order.SubjectType) + ":" + order.SubjectID,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 条件状态迁移:仅 CREATED → SETTLED;rows=0 说明已被并发处理(锁内不应发生,作最后防线)
|
||||||
|
ok, err := chargeDao.ChargeOrder.UpdateToSettled(ctx, &pricingDto.SettleChargeOrderReq{
|
||||||
|
Id: order.Id,
|
||||||
|
ActualAmount: actualAmount,
|
||||||
|
Usage: usage,
|
||||||
|
RuleSnapshot: order.RuleSnapshot,
|
||||||
|
SettleTime: gtime.Now(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return ErrOrderAlreadyHandled
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if alreadySettled {
|
||||||
|
order.Status = pricingConsts.ChargeOrderStatusSettled
|
||||||
|
order.ActualAmount = settledAmount
|
||||||
|
order.Usage = usage
|
||||||
|
return settledAmount, nil
|
||||||
|
}
|
||||||
|
order.Status = pricingConsts.ChargeOrderStatusSettled
|
||||||
|
order.ActualAmount = actualAmount
|
||||||
|
order.Usage = usage
|
||||||
|
return actualAmount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sumRecordedFee 按次已记录费用合计:feeByModel 各值为调用方按次调 shop /calc 的返回费
|
||||||
|
// (calculator.charge 内部已 ceilFen 到分),直接相加再归一化到分(去浮点噪声)。
|
||||||
|
// 负值防御性忽略(异常上报不抵减)。per_item/per_second 取消补收与 per_token 结算的 token 金额据此实收。
|
||||||
|
func sumRecordedFee(feeByModel map[string]float64) float64 {
|
||||||
|
var total float64
|
||||||
|
for _, fee := range feeByModel {
|
||||||
|
if fee < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
total += fee
|
||||||
|
}
|
||||||
|
return ceilFen(roundCost(total))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 失败(不扣费) ======================
|
||||||
|
|
||||||
|
// Fail 失败处理:执行失败/无产出,不扣费、不动钱,仅状态迁移 CREATED → FAILED。幂等。
|
||||||
|
func (s *pricing) Fail(ctx context.Context, req *pricingDto.FailReq) (*pricingDto.ChargeOrderInfo, error) {
|
||||||
|
order, err := s.resolveOrder(ctx, req.OrderId, req.SubjectType, req.SubjectID, req.BizOrderNo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 幂等:已失败直接返回
|
||||||
|
if order.Status == pricingConsts.ChargeOrderStatusFailed {
|
||||||
|
info := s.toOrderInfo(order)
|
||||||
|
return &info, nil
|
||||||
|
}
|
||||||
|
if order.Status != pricingConsts.ChargeOrderStatusCreated {
|
||||||
|
return nil, errors.New("计费单状态不可转失败(已结算或状态异常)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 与 Settle/Cancel 同一用户锁串行,防止结算/失败竞争(锁内重读为权威判定)
|
||||||
|
err = walletService.Account.WithUserLock(ctx, order.UserId, func(ctx context.Context) error {
|
||||||
|
fresh, err := chargeDao.ChargeOrder.GetOrder(ctx, &pricingDto.GetChargeOrderReq{Id: order.Id})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fresh == nil {
|
||||||
|
return errors.New("计费单不存在")
|
||||||
|
}
|
||||||
|
if fresh.Status == pricingConsts.ChargeOrderStatusFailed {
|
||||||
|
return nil // 已失败(幂等)
|
||||||
|
}
|
||||||
|
if fresh.Status != pricingConsts.ChargeOrderStatusCreated {
|
||||||
|
return errors.New("计费单状态不可转失败(已结算或状态异常)")
|
||||||
|
}
|
||||||
|
ok, err := chargeDao.ChargeOrder.UpdateToFailed(ctx, &pricingDto.FailChargeOrderReq{Id: order.Id})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return ErrOrderAlreadyHandled
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
order.Status = pricingConsts.ChargeOrderStatusFailed
|
||||||
|
info := s.toOrderInfo(order)
|
||||||
|
return &info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 算费(不建单不扣费) ======================
|
||||||
|
|
||||||
|
// CalcFee 按计价配置与用量计算费用(不建单不扣费)。未配置/未启用 → 报错(model-gateway 据此阻塞调用)。
|
||||||
|
// model subject:chargeMode 可空 = 配置 unit;快照 = 整个 rules。复用 getEnabledConfig + resolveChargeMode + 计算器。
|
||||||
|
func (s *pricing) CalcFee(ctx context.Context, req *pricingDto.CalcFeeReq) (*pricingDto.CalcFeeRes, error) {
|
||||||
|
cfg, err := s.getEnabledConfig(ctx, pricingConsts.SubjectType(req.SubjectType), req.SubjectID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
chargeMode, ruleSnapshot, err := s.resolveChargeMode(ctx, cfg, &pricingDto.OpenOrderReq{
|
||||||
|
SubjectType: req.SubjectType, SubjectID: req.SubjectID, ChargeMode: req.ChargeMode,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var usage ChargeUsage
|
||||||
|
if err := gconv.Struct(req.Usage, &usage); err != nil {
|
||||||
|
return nil, fmt.Errorf("用量解析失败: %v", err)
|
||||||
|
}
|
||||||
|
c, err := getCalculator(cfg.SubjectType, chargeMode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ruleJSON, err := json.Marshal(ruleSnapshot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("费率快照序列化失败: %v", err)
|
||||||
|
}
|
||||||
|
rules, err := c.validate(string(ruleJSON))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cost, err := c.charge(rules, &usage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pricingDto.CalcFeeRes{Cost: cost}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 查询 ======================
|
||||||
|
|
||||||
|
// GetOrderInfo 查询计费单(复用 resolveOrder + toOrderInfo;工作流结算前取 CreatedAt 算时长)
|
||||||
|
func (s *pricing) GetOrderInfo(ctx context.Context, req *pricingDto.GetOrderInfoReq) (*pricingDto.ChargeOrderInfo, error) {
|
||||||
|
order, err := s.resolveOrder(ctx, req.Id, req.SubjectType, req.SubjectID, req.BizOrderNo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
info := s.toOrderInfo(order)
|
||||||
|
return &info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 内部辅助 ======================
|
||||||
|
|
||||||
|
// resolveOrder 按ID或 subject_type+subject_id+biz_order_no 定位计费单
|
||||||
|
func (s *pricing) resolveOrder(ctx context.Context, orderId int64, subjectType, subjectID, bizOrderNo string) (*pricingEntity.ChargeOrder, error) {
|
||||||
|
if orderId > 0 {
|
||||||
|
order, err := chargeDao.ChargeOrder.GetOrder(ctx, &pricingDto.GetChargeOrderReq{Id: orderId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if order == nil {
|
||||||
|
return nil, errors.New("计费单不存在")
|
||||||
|
}
|
||||||
|
return order, nil
|
||||||
|
}
|
||||||
|
if subjectType == "" || subjectID == "" || bizOrderNo == "" {
|
||||||
|
return nil, errors.New("orderId 与 subjectType/subjectId/bizOrderNo 须二选一")
|
||||||
|
}
|
||||||
|
order, err := chargeDao.ChargeOrder.GetOrder(ctx, &pricingDto.GetChargeOrderReq{SubjectType: subjectType, SubjectID: subjectID, BizOrderNo: bizOrderNo})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if order == nil {
|
||||||
|
return nil, errors.New("计费单不存在")
|
||||||
|
}
|
||||||
|
return order, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toOrderInfo 计费单实体转响应
|
||||||
|
func (s *pricing) toOrderInfo(e *pricingEntity.ChargeOrder) pricingDto.ChargeOrderInfo {
|
||||||
|
info := pricingDto.ChargeOrderInfo{
|
||||||
|
ID: e.Id,
|
||||||
|
SubjectType: string(e.SubjectType),
|
||||||
|
SubjectID: e.SubjectID,
|
||||||
|
BizOrderNo: e.BizOrderNo,
|
||||||
|
UserId: e.UserId,
|
||||||
|
ChargeMode: string(e.ChargeMode),
|
||||||
|
Status: int(e.Status),
|
||||||
|
ActualAmount: e.ActualAmount,
|
||||||
|
Usage: e.Usage,
|
||||||
|
RuleSnapshot: e.RuleSnapshot,
|
||||||
|
}
|
||||||
|
if e.CreatedAt != nil {
|
||||||
|
info.CreatedAt = e.CreatedAt.String()
|
||||||
|
}
|
||||||
|
if e.SettleTime != nil {
|
||||||
|
info.SettleTime = e.SettleTime.String()
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/http"
|
||||||
|
|
||||||
|
pricingConsts "shop-user-trade/consts/pricing"
|
||||||
|
pricingDto "shop-user-trade/model/dto/pricing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// modelGatewayModelItem 解析 /listModelManage 响应单条(仅取枚举所需字段)。
|
||||||
|
// model-gateway 的 ModelType 为 *int(consts/model/model_type.go:59 `type ModelType = *int`),可为 null → 用 *int 安全解析。
|
||||||
|
type modelGatewayModelItem struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
ModelName string `json:"modelName"`
|
||||||
|
ModelType *int `json:"modelType"` // 编码:100推理/200图片/300音频/600视频(*int,可能 null)
|
||||||
|
SystemModel *bool `json:"systemModel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSubjects 计价对象枚举:固定 workflow + 实时 model-gateway 系统模型 + 固定业务模块(spec §7)。
|
||||||
|
// 每项查 pricing_config 是否已配置(hasConfig/enabled);model-gateway 不可用时直接报错。
|
||||||
|
func (s *pricing) ListSubjects(ctx context.Context, req *pricingDto.SubjectListReq) (*pricingDto.SubjectListRes, error) {
|
||||||
|
res := &pricingDto.SubjectListRes{Subjects: []pricingDto.SubjectInfo{}}
|
||||||
|
|
||||||
|
// 1. workflow(固定)
|
||||||
|
res.Subjects = append(res.Subjects, pricingDto.SubjectInfo{
|
||||||
|
SubjectType: string(pricingConsts.SubjectTypeWorkflow),
|
||||||
|
SubjectID: pricingConsts.WorkflowSubjectID,
|
||||||
|
Name: "工作流",
|
||||||
|
ChargeModes: []string{string(pricingConsts.ChargeModePerItem), string(pricingConsts.ChargeModePerSecond), string(pricingConsts.ChargeModePerToken)},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. 业务模块(固定写死)
|
||||||
|
for _, b := range pricingConsts.BusinessSubjects {
|
||||||
|
modes := make([]string, 0, len(b.ChargeModes))
|
||||||
|
for _, m := range b.ChargeModes {
|
||||||
|
modes = append(modes, string(m))
|
||||||
|
}
|
||||||
|
res.Subjects = append(res.Subjects, pricingDto.SubjectInfo{
|
||||||
|
SubjectType: string(pricingConsts.SubjectTypeBusiness),
|
||||||
|
SubjectID: b.SubjectID,
|
||||||
|
Name: b.Name,
|
||||||
|
ChargeModes: modes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. model-gateway 系统模型(实时;不可用直接报错)
|
||||||
|
models, err := s.listSystemModels(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, m := range models {
|
||||||
|
typeCode := 0
|
||||||
|
if m.ModelType != nil {
|
||||||
|
typeCode = *m.ModelType
|
||||||
|
}
|
||||||
|
res.Subjects = append(res.Subjects, pricingDto.SubjectInfo{
|
||||||
|
SubjectType: string(pricingConsts.SubjectTypeModel),
|
||||||
|
SubjectID: fmt.Sprintf("%d", m.Id),
|
||||||
|
Name: m.ModelName,
|
||||||
|
ModelType: modelTypeName(typeCode),
|
||||||
|
ModelTypeCode: typeCode,
|
||||||
|
//HasConfig: false, Enabled: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充 hasConfig/enabled(批量查 pricing_config)
|
||||||
|
//if err := s.fillSubjectConfig(ctx, res.Subjects); err != nil {
|
||||||
|
// return nil, err
|
||||||
|
//}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// listSystemModels 调 model-gateway /listModelManage(system_model=true)拉取系统模型
|
||||||
|
func (s *pricing) listSystemModels(ctx context.Context) ([]modelGatewayModelItem, error) {
|
||||||
|
// ⚠️ http.Get 内部已剥离 {code,message,data} 包裹层并 gconv.Struct(Data, target),
|
||||||
|
// 故 target 直接是 data 载荷 {list,total}(controller 修正 2026-08-28,原稿误传包裹结构会静默空列表)
|
||||||
|
res := &struct {
|
||||||
|
List []modelGatewayModelItem `json:"list"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
}{}
|
||||||
|
// GET 查询参数:偶数对 key/value;分页拉全量(总条数少,一次取大页)。
|
||||||
|
err := http.Get(ctx, "model-gateway/model/manage/listModelManage", nil, res, beans.Page{PageNum: 1, PageSize: 1000})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("调用model-gateway模型列表失败: %w", err)
|
||||||
|
}
|
||||||
|
var system []modelGatewayModelItem
|
||||||
|
for _, m := range res.List {
|
||||||
|
if m.SystemModel != nil && *m.SystemModel {
|
||||||
|
system = append(system, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return system, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fillSubjectConfig 按 (subject_type, subject_id) 查配置,填充 hasConfig/enabled
|
||||||
|
//func (s *pricing) fillSubjectConfig(ctx context.Context, items []pricingDto.SubjectInfo) error {
|
||||||
|
// for i := range items {
|
||||||
|
// cfg, err := chargeDao.PricingConfig.Get(ctx, &pricingDto.GetConfigReq{
|
||||||
|
// SubjectType: items[i].SubjectType, SubjectID: items[i].SubjectID,
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// if cfg != nil {
|
||||||
|
// items[i].HasConfig = true
|
||||||
|
// items[i].Enabled = cfg.Enabled
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
|
||||||
|
// modelTypeName 模型类型编码 → 名称(管理端分组展示)
|
||||||
|
func modelTypeName(code int) string {
|
||||||
|
switch code {
|
||||||
|
case pricingConsts.ModelTypeReasoning:
|
||||||
|
return "推理"
|
||||||
|
case pricingConsts.ModelTypeImage:
|
||||||
|
return "图片"
|
||||||
|
case pricingConsts.ModelTypeAudio:
|
||||||
|
return "音频"
|
||||||
|
case pricingConsts.ModelTypeVideo:
|
||||||
|
return "视频"
|
||||||
|
case pricingConsts.ModelTypeVector:
|
||||||
|
return "向量"
|
||||||
|
case pricingConsts.ModelTypeOmni:
|
||||||
|
return "多模态"
|
||||||
|
default:
|
||||||
|
return "其他"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListChargeModes 按 (subjectType, subjectId) 返回主体自身可选计费方式(spec 2026-09-01)。
|
||||||
|
// model 无需选择不入参(subjectType 校验层拦截);workflow/business 纯查常量,零外部依赖。
|
||||||
|
func (s *pricing) ListChargeModes(ctx context.Context, req *pricingDto.ChargeModeListReq) (*pricingDto.ChargeModeListRes, error) {
|
||||||
|
switch pricingConsts.SubjectType(req.SubjectType) {
|
||||||
|
case pricingConsts.SubjectTypeWorkflow:
|
||||||
|
if req.SubjectID != pricingConsts.WorkflowSubjectID {
|
||||||
|
return nil, fmt.Errorf("workflow 主体 subjectId 必须为 %q", pricingConsts.WorkflowSubjectID)
|
||||||
|
}
|
||||||
|
return &pricingDto.ChargeModeListRes{
|
||||||
|
SubjectType: req.SubjectType,
|
||||||
|
SubjectID: req.SubjectID,
|
||||||
|
ChargeModes: toChargeModeInfo(pricingConsts.WorkflowChargeModes),
|
||||||
|
}, nil
|
||||||
|
case pricingConsts.SubjectTypeBusiness:
|
||||||
|
for _, b := range pricingConsts.BusinessSubjects {
|
||||||
|
if b.SubjectID == req.SubjectID {
|
||||||
|
return &pricingDto.ChargeModeListRes{
|
||||||
|
SubjectType: req.SubjectType,
|
||||||
|
SubjectID: req.SubjectID,
|
||||||
|
ChargeModes: toChargeModeInfo(b.ChargeModes),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("未知业务模块: %s", req.SubjectID)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("不支持的计价对象类型: %s", req.SubjectType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// toChargeModeInfo 计费方式 → {mode, name}(name 未命中回退 mode,避免展示空串)
|
||||||
|
func toChargeModeInfo(modes []pricingConsts.ChargeMode) []pricingDto.ChargeModeInfo {
|
||||||
|
items := make([]pricingDto.ChargeModeInfo, 0, len(modes))
|
||||||
|
for _, m := range modes {
|
||||||
|
name, ok := pricingConsts.ChargeModeNames[m]
|
||||||
|
if !ok {
|
||||||
|
name = string(m)
|
||||||
|
}
|
||||||
|
items = append(items, pricingDto.ChargeModeInfo{Mode: string(m), Name: name})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/utils"
|
||||||
|
|
||||||
|
walletConsts "shop-user-trade/consts/wallet"
|
||||||
|
walletDao "shop-user-trade/dao/wallet"
|
||||||
|
walletDto "shop-user-trade/model/dto/wallet"
|
||||||
|
walletEntity "shop-user-trade/model/entity/wallet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Account 钱包服务:管钱(钱包账务 + 流水)。
|
||||||
|
// 事务与 Redis 锁统一在本层编排:Recharge 持 common 锁 + 单事务原子完成;
|
||||||
|
// pricing 结算复用本层 WithUserLock 编排「钱包账务 + 计费单状态」。
|
||||||
|
// DAO 只提供事务透明的数据原语(Change),不加锁、不开事务、不互相调用。
|
||||||
|
var Account = new(account)
|
||||||
|
|
||||||
|
type account struct{}
|
||||||
|
|
||||||
|
const (
|
||||||
|
walletLockTTL = 30 // 钱包 Redis 锁 TTL(秒);锁串行化跨节点互斥,DB 事务 + 行锁兜底正确性
|
||||||
|
walletLockRetry = 20 // 抢锁最多尝试次数(每次间隔 500ms,≈10s),避免无限阻塞
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrWalletNotFound 钱包不存在(service 层业务错误;DAO 不定义业务错误,见分层规范)
|
||||||
|
ErrWalletNotFound = errors.New("钱包不存在")
|
||||||
|
// ErrWalletDisabled 钱包状态不可用
|
||||||
|
ErrWalletDisabled = errors.New("钱包状态不可用")
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetAccountByUserId 根据用户ID获取钱包信息
|
||||||
|
func (s *account) GetAccountByUserId(ctx context.Context, req *walletDto.GetAccountByUserIdReq) (*walletDto.AccountInfo, error) {
|
||||||
|
w, err := walletDao.Account.Get(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
g.Log().Errorf(ctx, "获取用户钱包失败, userId: %d, error: %v", req.UserId, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if w == nil {
|
||||||
|
return nil, ErrWalletNotFound
|
||||||
|
}
|
||||||
|
return &walletDto.AccountInfo{
|
||||||
|
ID: w.Id,
|
||||||
|
UserID: w.UserID,
|
||||||
|
Balance: w.Balance,
|
||||||
|
Currency: w.Currency,
|
||||||
|
Status: int(w.Status),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUserLock 持用户钱包 Redis 锁执行 fn(跨节点串行化单钱包互斥)。
|
||||||
|
// 锁用 common 统一实现 utils.WithLock(uuid token + SET NX EX + WATCH/MULTI 归属释放 + 自动续期)。
|
||||||
|
// 事务由调用方自行开启(gfdb.DB(ctx).Transaction),账务原语在事务内自动继承,本方法不开事务。
|
||||||
|
func (s *account) WithUserLock(ctx context.Context, userID int64, fn func(ctx context.Context) error) error {
|
||||||
|
_, err := utils.WithLock(ctx, fmt.Sprintf("wallet:lock:%d", userID), walletLockTTL, fn, walletLockRetry)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recharge 充值(钱包不存在则自动创建)。
|
||||||
|
// 事务在 service 层编排:持钱包锁 → 单事务内「建钱包 + income 流水 + 余额增加」原子完成。
|
||||||
|
func (s *account) Recharge(ctx context.Context, req *walletDto.RechargeReq) error {
|
||||||
|
if req.Amount <= 0 {
|
||||||
|
return errors.New("充值金额必须大于 0")
|
||||||
|
}
|
||||||
|
currency := req.Currency
|
||||||
|
if currency == "" {
|
||||||
|
currency = "CNY"
|
||||||
|
}
|
||||||
|
err := s.WithUserLock(ctx, req.UserId, func(ctx context.Context) error {
|
||||||
|
return gfdb.DB(ctx).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||||
|
w, err := walletDao.Account.GetForUpdate(ctx, &walletDto.GetAccountByUserIdReq{UserId: req.UserId})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if w == nil {
|
||||||
|
if _, err = walletDao.Account.Create(ctx, &walletDto.CreateAccountReq{UserId: req.UserId, UserName: req.UserName, Status: 1, Currency: currency}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return walletDao.Account.Change(ctx, &walletDto.ChangeAccountReq{
|
||||||
|
UserId: req.UserId,
|
||||||
|
DeltaBalance: req.Amount,
|
||||||
|
Type: walletConsts.WalletLogTypeIncome,
|
||||||
|
Amount: req.Amount,
|
||||||
|
OrderNo: req.OrderNo,
|
||||||
|
Description: req.Description,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWalletLogs 分页获取钱包流水
|
||||||
|
func (s *account) GetWalletLogs(ctx context.Context, req *walletDto.GetAccountLogsReq) (*walletDto.AccountLogData, error) {
|
||||||
|
logs, total, err := walletDao.Account.ListLogs(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var infos []walletDto.AccountLogInfo
|
||||||
|
for i := range logs {
|
||||||
|
infos = append(infos, s.logEntityToInfo(&logs[i]))
|
||||||
|
}
|
||||||
|
return &walletDto.AccountLogData{Logs: infos, Total: total}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// logEntityToInfo 流水实体转Info
|
||||||
|
func (s *account) logEntityToInfo(e *walletEntity.AccountLog) walletDto.AccountLogInfo {
|
||||||
|
info := walletDto.AccountLogInfo{}
|
||||||
|
_ = gconv.Struct(e, &info)
|
||||||
|
info.ID = e.Id
|
||||||
|
info.Type = string(e.Type)
|
||||||
|
if e.CreatedAt != nil {
|
||||||
|
info.CreatedAt = e.CreatedAt.String()
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user