From 789f3b43e44e6a3f19a7a606550843283f719c9f Mon Sep 17 00:00:00 2001 From: xiezhouwei Date: Tue, 23 Jun 2026 11:26:00 +0800 Subject: [PATCH] fix: prevent float-to-int truncation turning small billing amounts (e.g. 0.01) into 0 in use-price paths\n\n- service/quota.go: replace IntPart() with Round(0).IntPart() + safety net\n- relay/helper/price.go: replace int(float64) with int(math.Round()) + safety net\n- controller/channel-test.go: same fix for test billing calculation\n- All use-price paths now round instead of truncate, and guarantee minimum 1 quota when effective pricing > 0 --- controller/channel-test.go | 6 +++++- relay/helper/price.go | 12 ++++++++++-- service/quota.go | 14 ++++++++++++-- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/controller/channel-test.go b/controller/channel-test.go index e4f6dff..dca2aa0 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -645,7 +645,11 @@ func testChannel(channel *model.Channel, testModel string, endpointType string, quota = 1 } } else { - quota = int(priceData.ModelPrice * common.QuotaPerUnit) + quota = int(math.Round(priceData.ModelPrice * common.QuotaPerUnit)) + // 有有效定价但舍入为 0 时保底为 1 + if quota == 0 && priceData.ModelPrice > 0 { + quota = 1 + } } tok := time.Now() milliseconds := tok.Sub(tik).Milliseconds() diff --git a/relay/helper/price.go b/relay/helper/price.go index d9b278c..6b1b491 100644 --- a/relay/helper/price.go +++ b/relay/helper/price.go @@ -172,7 +172,11 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens effModelPrice = effModelPrice * meta.ImagePriceRatio modelPrice = modelPrice * meta.ImagePriceRatio // 保持 ModelPrice 字段与 imagePriceRatio 一致(供日志展示) } - preConsumedQuota = int(effModelPrice * common.QuotaPerUnit * groupRatioInfo.GroupRatio) + preConsumedQuota = int(math.Round(effModelPrice * common.QuotaPerUnit * groupRatioInfo.GroupRatio)) + // 有有效定价但舍入为 0 时保底为 1,避免计费为 0 + if preConsumedQuota == 0 && effModelPrice > 0 && groupRatioInfo.GroupRatio > 0 { + preConsumedQuota = 1 + } } // check if free model pre-consume is disabled @@ -276,7 +280,11 @@ func ModelPriceHelperPerCall(c *gin.Context, info *relaycommon.RelayInfo) (types markupDisc := effectiveMarkupDiscountPercent(c, info, channelID, info.OriginModelName) globalPrice, _ := ratio_setting.GetModelPrice(info.OriginModelName, false) effModelPrice := model.EffectiveModelPrice(modelPrice, globalPrice, chDisc, markupDisc) - quota := int(effModelPrice * common.QuotaPerUnit * groupRatioInfo.GroupRatio) + quota := int(math.Round(effModelPrice * common.QuotaPerUnit * groupRatioInfo.GroupRatio)) + // 有有效定价但舍入为 0 时保底为 1,避免计费为 0 + if quota == 0 && effModelPrice > 0 && groupRatioInfo.GroupRatio > 0 { + quota = 1 + } // 免费模型检测(与 ModelPriceHelper 对齐) freeModel := false diff --git a/service/quota.go b/service/quota.go index 0b966b6..c6d0e17 100644 --- a/service/quota.go +++ b/service/quota.go @@ -66,7 +66,12 @@ func calculateAudioQuota(info QuotaInfo) int { // 新公式:固定价格 = 渠道固定价 * 成本折扣率% + 全局固定价 * 加价折扣率% effModelPrice := model.EffectiveModelPrice(info.ModelPrice, info.GlobalModelPrice, costDisc, markupDisc) quota := decimal.NewFromFloat(effModelPrice).Mul(quotaPerUnit).Mul(groupRatio) - return int(quota.IntPart()) + quotaInt := int(quota.Round(0).IntPart()) + // 有有效定价但舍入为 0 时保底为 1,避免计费为 0 + if info.UsePrice && quotaInt == 0 && quota.GreaterThan(decimal.Zero) { + quotaInt = 1 + } + return quotaInt } completionRatio := decimal.NewFromFloat(ratio_setting.GetCompletionRatio(info.ModelName)) @@ -101,7 +106,12 @@ func calculateAudioQuota(info QuotaInfo) int { quota = decimal.NewFromInt(1) } - return int(quota.Round(0).IntPart()) + quotaInt := int(quota.Round(0).IntPart()) + // 有有效输入倍率但舍入为 0 时保底为 1(如 0.01 经 Round 后为 0) + if quotaInt == 0 && effInputRate > 0 && quota.GreaterThan(decimal.Zero) { + quotaInt = 1 + } + return quotaInt } func PreWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.RealtimeUsage) error {