From 9b1aa1288ddb39c903c8d0b7e059c2ebe23ec7de Mon Sep 17 00:00:00 2001 From: qhd <1766646056@qq.com> Date: Fri, 28 Aug 2026 17:44:22 +0800 Subject: [PATCH] =?UTF-8?q?refactor(pricing):=20entity/DTO=20=E6=8D=A2=20s?= =?UTF-8?q?ubject=5Ftype+subject=5Fid=EF=BC=88=E5=90=AB=20subject=5Fdto=20?= =?UTF-8?q?=E6=9E=9A=E4=B8=BE=20DTO=EF=BC=89=20Co-Authored-By:=20Claude=20?= =?UTF-8?q??= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/dto/pricing/charge_dto.go | 138 +++++++++++++++++++++++++ model/dto/pricing/config_dto.go | 63 +++++++++++ model/dto/pricing/subject_dto.go | 27 +++++ model/entity/pricing/charge_order.go | 53 ++++++++++ model/entity/pricing/pricing_config.go | 56 ++++++++++ 5 files changed, 337 insertions(+) create mode 100644 model/dto/pricing/charge_dto.go create mode 100644 model/dto/pricing/config_dto.go create mode 100644 model/dto/pricing/subject_dto.go create mode 100644 model/entity/pricing/charge_order.go create mode 100644 model/entity/pricing/pricing_config.go diff --git a/model/dto/pricing/charge_dto.go b/model/dto/pricing/charge_dto.go new file mode 100644 index 0000000..7d5e01a --- /dev/null +++ b/model/dto/pricing/charge_dto.go @@ -0,0 +1,138 @@ +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 string `json:"usage" v:"required" dc:"用量JSON:{durationSec,itemCount,tokensByModel,...}"` +} + +// ====================== 取消(按已消耗用量结算) ====================== + +// 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 string `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:"失败原因"` +} + +// ====================== 查询 ====================== + +// GetOrderReq 查询计费单请求 +type GetOrderReq struct { + g.Meta `path:"/get_order" method:"get" tags:"计价扣费" summary:"查询计费单" dc:"按ID或 subjectType+subjectId+bizOrderNo 查询"` + + 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 为空时必填)"` +} + +// ListOrdersReq 分页查询计费单请求 +type ListOrdersReq struct { + g.Meta `path:"/orders" 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:"每页大小"` +} + +// ChargeOrderList 计费单列表 +type ChargeOrderList struct { + Orders []ChargeOrderInfo `json:"orders"` + Total int `json:"total"` +} + +// 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 string `json:"usage"` + RuleSnapshot string `json:"ruleSnapshot"` + SettleTime string `json:"settleTime"` + CreatedAt string `json:"createdAt"` +} + +// ====================== 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 string `json:"ruleSnapshot"` +} + +// SettleChargeOrderReq DAO 结算迁移(CREATED→SETTLED,条件更新幂等) +type SettleChargeOrderReq struct { + Id int64 `json:"id"` + ActualAmount float64 `json:"actualAmount"` + Usage string `json:"usage"` + RuleSnapshot string `json:"ruleSnapshot"` + SettleTime *gtime.Time `json:"settleTime"` +} + +// FailChargeOrderReq DAO 失败迁移(CREATED→FAILED,条件更新幂等) +type FailChargeOrderReq struct { + Id int64 `json:"id"` +} diff --git a/model/dto/pricing/config_dto.go b/model/dto/pricing/config_dto.go new file mode 100644 index 0000000..72dce38 --- /dev/null +++ b/model/dto/pricing/config_dto.go @@ -0,0 +1,63 @@ +package dto + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +// ====================== 配置管理 ====================== + +// SaveConfigReq 新增/更新计价配置请求(费率全 DB 配置,改价/加档只改 rules) +type SaveConfigReq 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 string `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停用"` +} + +// SaveConfigData 保存计价配置数据 +type SaveConfigData struct { + Id int64 `json:"id"` +} + +// GetConfigReq 查询计价配置请求 +type GetConfigReq 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"` +} + +// ListConfigsReq 分页查询计价配置请求 +type ListConfigsReq struct { + g.Meta `path:"/config/list" method:"get" tags:"计价配置" summary:"分页查询计价配置" dc:"可按 subjectType/enabled 过滤"` + + SubjectType string `json:"subjectType" dc:"计价对象类型"` + Enabled int `json:"enabled" dc:"1启用 0停用,0不过滤"` + Page int `json:"page" v:"required|min:1" dc:"页码"` + PageSize int `json:"pageSize" v:"required|min:1|max:100" dc:"每页大小"` +} + +// PricingConfigList 计价配置列表 +type PricingConfigList struct { + Configs []PricingConfigInfo `json:"configs"` + Total int `json:"total"` +} + +// PricingConfigInfo 计价配置信息 +type PricingConfigInfo struct { + ID int64 `json:"id"` + SubjectType string `json:"subjectType"` + SubjectID string `json:"subjectId"` + Rules string `json:"rules"` + MinBalance float64 `json:"minBalance"` + Currency string `json:"currency"` + Enabled int `json:"enabled"` + Version int64 `json:"version"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} diff --git a/model/dto/pricing/subject_dto.go b/model/dto/pricing/subject_dto.go new file mode 100644 index 0000000..5c2ff9c --- /dev/null +++ b/model/dto/pricing/subject_dto.go @@ -0,0 +1,27 @@ +package dto + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +// SubjectsReq 计价对象枚举请求 +type SubjectsReq struct { + g.Meta `path:"/subjects" method:"get" tags:"计价配置" summary:"计价对象枚举" dc:"workflow + 实时model-gateway系统模型 + 固定业务模块"` +} + +// 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 用(可选的计费方式) + HasConfig bool `json:"hasConfig"` // 是否已配置价格 + Enabled int `json:"enabled"` // 已配置时的启用状态 +} + +// SubjectListRes 计价对象枚举响应 +type SubjectListRes struct { + Subjects []SubjectInfo `json:"subjects"` +} diff --git a/model/entity/pricing/charge_order.go b/model/entity/pricing/charge_order.go new file mode 100644 index 0000000..0f450a3 --- /dev/null +++ b/model/entity/pricing/charge_order.go @@ -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 string `orm:"usage" json:"usage" description:"实际用量JSON"` + RuleSnapshot string `orm:"rule_snapshot" json:"ruleSnapshot" description:"结算用费率快照JSON(防改价影响在途单)"` + SettleTime *gtime.Time `orm:"settle_time" json:"settleTime" description:"结算时间"` +} diff --git a/model/entity/pricing/pricing_config.go b/model/entity/pricing/pricing_config.go new file mode 100644 index 0000000..f24c56e --- /dev/null +++ b/model/entity/pricing/pricing_config.go @@ -0,0 +1,56 @@ +package entity + +import ( + "encoding/json" + + 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 + Version string +} + +var PricingConfigCol = pricingConfigCol{ + SQLBaseCol: beans.DefSQLBaseCol, + SubjectType: "subject_type", + SubjectID: "subject_id", + Rules: "rules", + MinBalance: "min_balance", + Currency: "currency", + Enabled: "enabled", + Version: "version", +} + +// 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 string `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停用"` + Version int64 `orm:"version" json:"version" description:"乐观锁版本号"` +} + +// Unit 从 rules 中解析模型计费单位(仅 subject_type=model 有效,其余返回空)。 +// service 层 per_token 结算/建单解析计费方式时使用,避免重复解析 rules。 +func (e *PricingConfig) Unit() pricingConsts.ChargeMode { + var cfg struct { + Unit pricingConsts.ChargeMode `json:"unit"` + } + if err := json.Unmarshal([]byte(e.Rules), &cfg); err != nil { + return "" + } + return cfg.Unit +}