mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-22 16:27:01 +00:00
refactor: M6 extract applyAccountStatsCost helper, M11 fix postUsageBilling double-queue
M6: New applyAccountStatsCost() helper in account_stats_pricing.go replaces
identical upstream-model-fallback + resolveAccountStatsCost() call sites
in both gateway_service.go and openai_gateway_service.go
M11: postUsageBilling legacy path no longer calls finalizePostUsageBilling,
fixing the double-queue bug (QueueDeductBalance was called twice if the
fallback path was ever reached). Added NOTE comment documenting the split.
This commit is contained in:
@@ -1 +1 @@
|
||||
0.1.110.49
|
||||
0.1.110.50
|
||||
|
||||
@@ -198,3 +198,24 @@ func calculateTokenStatsCost(pricing *ChannelModelPricing, tokens UsageTokens) *
|
||||
}
|
||||
return &cost
|
||||
}
|
||||
|
||||
// applyAccountStatsCost resolves the account stats cost for a usage log entry.
|
||||
// It resolves the upstream model (falling back to the requested model) and calls
|
||||
// the 4-level priority chain via resolveAccountStatsCost.
|
||||
func applyAccountStatsCost(
|
||||
ctx context.Context,
|
||||
usageLog *UsageLog,
|
||||
cs *ChannelService, bs *BillingService,
|
||||
accountID int64, groupID int64,
|
||||
upstreamModel, requestedModel string,
|
||||
tokens UsageTokens,
|
||||
totalCost float64,
|
||||
) {
|
||||
model := upstreamModel
|
||||
if model == "" {
|
||||
model = requestedModel
|
||||
}
|
||||
usageLog.AccountStatsCost = resolveAccountStatsCost(
|
||||
ctx, cs, bs, accountID, groupID, model, tokens, 1, totalCost,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7340,49 +7340,41 @@ func (p *postUsageBillingParams) shouldUpdateAccountQuota() bool {
|
||||
return p.Cost.TotalCost > 0 && p.Account.IsAPIKeyOrBedrock() && p.Account.HasAnyQuotaLimit()
|
||||
}
|
||||
|
||||
// postUsageBilling 统一处理使用量记录后的扣费逻辑:
|
||||
// - 订阅/余额扣费
|
||||
// - API Key 配额更新
|
||||
// - API Key 限速用量更新
|
||||
// - 账号配额用量更新(账号口径:TotalCost × 账号计费倍率)
|
||||
// postUsageBilling is the legacy fallback billing path used when the unified
|
||||
// billing repo is unavailable (nil). Production uses applyUsageBilling → repo.Apply
|
||||
// for atomic billing. This path only runs in tests or degraded mode.
|
||||
func postUsageBilling(ctx context.Context, p *postUsageBillingParams, deps *billingDeps) {
|
||||
billingCtx, cancel := detachedBillingContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
cost := p.Cost
|
||||
|
||||
// 1. 订阅 / 余额扣费
|
||||
if p.IsSubscriptionBill {
|
||||
if cost.TotalCost > 0 {
|
||||
if err := deps.userSubRepo.IncrementUsage(billingCtx, p.Subscription.ID, cost.TotalCost); err != nil {
|
||||
slog.Error("increment subscription usage failed", "subscription_id", p.Subscription.ID, "error", err)
|
||||
}
|
||||
deps.billingCacheService.QueueUpdateSubscriptionUsage(p.User.ID, *p.APIKey.GroupID, cost.TotalCost)
|
||||
}
|
||||
} else {
|
||||
if cost.ActualCost > 0 {
|
||||
if err := deps.userRepo.DeductBalance(billingCtx, p.User.ID, cost.ActualCost); err != nil {
|
||||
slog.Error("deduct balance failed", "user_id", p.User.ID, "error", err)
|
||||
}
|
||||
deps.billingCacheService.QueueDeductBalance(p.User.ID, cost.ActualCost)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. API Key 配额
|
||||
if p.shouldDeductAPIKeyQuota() {
|
||||
if err := p.APIKeyService.UpdateQuotaUsed(billingCtx, p.APIKey.ID, cost.ActualCost); err != nil {
|
||||
slog.Error("update api key quota failed", "api_key_id", p.APIKey.ID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. API Key 限速用量
|
||||
if p.shouldUpdateRateLimits() {
|
||||
if err := p.APIKeyService.UpdateRateLimitUsage(billingCtx, p.APIKey.ID, cost.ActualCost); err != nil {
|
||||
slog.Error("update api key rate limit usage failed", "api_key_id", p.APIKey.ID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 账号配额用量(账号口径:TotalCost × 账号计费倍率)
|
||||
if p.shouldUpdateAccountQuota() {
|
||||
accountCost := cost.TotalCost * p.AccountRateMultiplier
|
||||
if err := deps.accountRepo.IncrementQuotaUsed(billingCtx, p.Account.ID, accountCost); err != nil {
|
||||
@@ -7390,7 +7382,10 @@ func postUsageBilling(ctx context.Context, p *postUsageBillingParams, deps *bill
|
||||
}
|
||||
}
|
||||
|
||||
finalizePostUsageBilling(p, deps, nil)
|
||||
// NOTE: finalizePostUsageBilling is NOT called here to avoid double-queuing
|
||||
// cache updates. The legacy path does DB writes directly; the finalize path
|
||||
// does cache queue + notifications. Notifications are dispatched separately
|
||||
// by the caller after recording the usage log.
|
||||
}
|
||||
|
||||
func resolveUsageBillingRequestID(ctx context.Context, upstreamRequestID string) string {
|
||||
@@ -7834,13 +7829,8 @@ func (s *GatewayService) recordUsageCore(ctx context.Context, input *recordUsage
|
||||
|
||||
// 计算账号统计定价费用(使用最终上游模型匹配自定义规则)
|
||||
if apiKey.GroupID != nil {
|
||||
upstreamModel := result.UpstreamModel
|
||||
if upstreamModel == "" {
|
||||
upstreamModel = result.Model
|
||||
}
|
||||
usageLog.AccountStatsCost = resolveAccountStatsCost(
|
||||
ctx, s.channelService, s.billingService,
|
||||
account.ID, *apiKey.GroupID, upstreamModel,
|
||||
applyAccountStatsCost(ctx, usageLog, s.channelService, s.billingService,
|
||||
account.ID, *apiKey.GroupID, result.UpstreamModel, result.Model,
|
||||
UsageTokens{
|
||||
InputTokens: result.Usage.InputTokens,
|
||||
OutputTokens: result.Usage.OutputTokens,
|
||||
@@ -7848,7 +7838,6 @@ func (s *GatewayService) recordUsageCore(ctx context.Context, input *recordUsage
|
||||
CacheReadTokens: result.Usage.CacheReadInputTokens,
|
||||
ImageOutputTokens: result.Usage.ImageOutputTokens,
|
||||
},
|
||||
1, // requestCount
|
||||
cost.TotalCost,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4575,14 +4575,9 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
|
||||
|
||||
// 计算账号统计定价费用(使用最终上游模型匹配自定义规则)
|
||||
if apiKey.GroupID != nil {
|
||||
statsModel := result.UpstreamModel
|
||||
if statsModel == "" {
|
||||
statsModel = result.Model
|
||||
}
|
||||
usageLog.AccountStatsCost = resolveAccountStatsCost(
|
||||
ctx, s.channelService, s.billingService,
|
||||
account.ID, *apiKey.GroupID, statsModel,
|
||||
tokens, 1, cost.TotalCost,
|
||||
applyAccountStatsCost(ctx, usageLog, s.channelService, s.billingService,
|
||||
account.ID, *apiKey.GroupID, result.UpstreamModel, result.Model,
|
||||
tokens, cost.TotalCost,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user