fix(billing): validate quantity parameters and harden quota calculations

Bound user-supplied count/duration parameters at request validation,
route ratio multipliers through guarded setters, and use saturating
int conversions in all quota math paths.
This commit is contained in:
CaIon
2026-07-07 00:21:06 +08:00
parent 45f0484dc1
commit d0bd8aac74
17 changed files with 293 additions and 19 deletions
+10 -2
View File
@@ -287,7 +287,7 @@ func calculateTextQuotaSummary(ctx *gin.Context, relayInfo *relaycommon.RelayInf
if !ratio.IsZero() && quotaCalculateDecimal.LessThanOrEqual(decimal.Zero) {
quotaCalculateDecimal = decimal.NewFromInt(1)
}
summary.Quota = int(quotaCalculateDecimal.Round(0).IntPart())
summary.Quota = decimalToQuota(quotaCalculateDecimal)
} else {
quotaCalculateDecimal := dModelPrice.Mul(dQuotaPerUnit).Mul(dGroupRatio)
quotaCalculateDecimal = quotaCalculateDecimal.Add(summary.ToolCallSurchargeQuota)
@@ -297,7 +297,7 @@ func calculateTextQuotaSummary(ctx *gin.Context, relayInfo *relaycommon.RelayInf
quotaCalculateDecimal = quotaCalculateDecimal.Mul(decimal.NewFromFloat(otherRatio))
}
}
summary.Quota = int(quotaCalculateDecimal.Round(0).IntPart())
summary.Quota = decimalToQuota(quotaCalculateDecimal)
}
if summary.TotalTokens == 0 {
@@ -309,6 +309,14 @@ func calculateTextQuotaSummary(ctx *gin.Context, relayInfo *relaycommon.RelayInf
return summary
}
// decimalToQuota converts a computed quota decimal to int with saturation
// (see common.QuotaFromFloat). Oversized multipliers (e.g. an absurd image
// generation count) must never wrap around and turn a charge into a credit.
func decimalToQuota(d decimal.Decimal) int {
f, _ := d.Round(0).Float64()
return common.QuotaFromFloat(f)
}
func usageSemanticFromUsage(relayInfo *relaycommon.RelayInfo, usage *dto.Usage) string {
if usage != nil && usage.UsageSemantic != "" {
return usage.UsageSemantic