Files
sub2api/backend/internal/service/openai_quota_reset_workflow.go
T

87 lines
3.1 KiB
Go

package service
import (
"context"
"log/slog"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
)
const (
OpenAIQuotaResetWarningCacheRefreshFailed = "reset_credit_cache_refresh_failed"
OpenAIQuotaResetWarningAccountRecoveryFailed = "account_state_recovery_failed"
OpenAIQuotaResetWarningAccountRefreshFailed = "account_state_refresh_failed"
)
type openAIQuotaResetWorkflowQuota interface {
QueryUsage(ctx context.Context, accountID int64) (*OpenAIQuotaUsage, error)
CachePostResetSnapshot(ctx context.Context, accountID int64, usage *OpenAIQuotaUsage) error
}
type openAIQuotaResetWorkflowRecoverer interface {
RecoverAccountState(ctx context.Context, accountID int64, options AccountRecoveryOptions) (*SuccessfulTestRecoveryResult, error)
}
// OpenAIQuotaResetPostProcessResult 汇总手动和自动用卡后的共享恢复结果。
// WarningCode 允许上游消费成功但本地恢复部分失败时仍然准确呈现状态。
type OpenAIQuotaResetPostProcessResult struct {
Quota *OpenAIQuotaUsage
Account *Account
CacheRefreshed bool
AccountStateRecovered bool
WarningCode string
}
// RunOpenAIQuotaResetPostProcess 按“解除限流、刷新额度缓存、刷新账号行”的固定顺序
// 执行消费后恢复,避免手动和自动入口在失败语义上逐渐分叉。
func RunOpenAIQuotaResetPostProcess(
ctx context.Context,
accountID int64,
quota openAIQuotaResetWorkflowQuota,
recoverer openAIQuotaResetWorkflowRecoverer,
loadAccount func(context.Context, int64) (*Account, error),
) OpenAIQuotaResetPostProcessResult {
result := OpenAIQuotaResetPostProcessResult{}
if recoverer == nil {
result.WarningCode = OpenAIQuotaResetWarningAccountRecoveryFailed
return result
}
if _, err := recoverer.RecoverAccountState(ctx, accountID, AccountRecoveryOptions{InvalidateToken: true}); err != nil {
slog.Warn("openai_quota_reset_account_recovery_failed", "account_id", accountID, "error_code", infraerrors.Reason(err))
result.WarningCode = OpenAIQuotaResetWarningAccountRecoveryFailed
return result
}
result.AccountStateRecovered = true
if quota != nil {
usage, usageErr := quota.QueryUsage(ctx, accountID)
switch {
case usageErr != nil || usage == nil:
slog.Warn("openai_quota_reset_cache_refresh_failed", "account_id", accountID, "error_code", infraerrors.Reason(usageErr))
result.WarningCode = OpenAIQuotaResetWarningCacheRefreshFailed
default:
if err := quota.CachePostResetSnapshot(ctx, accountID, usage); err != nil {
slog.Warn("openai_quota_reset_cache_refresh_failed", "account_id", accountID, "error_code", infraerrors.Reason(err))
result.WarningCode = OpenAIQuotaResetWarningCacheRefreshFailed
} else {
result.Quota = usage
result.CacheRefreshed = true
}
}
}
if loadAccount == nil {
return result
}
account, err := loadAccount(ctx, accountID)
if err != nil {
slog.Warn("openai_quota_reset_account_refresh_failed", "account_id", accountID, "error_code", infraerrors.Reason(err))
if result.WarningCode == "" {
result.WarningCode = OpenAIQuotaResetWarningAccountRefreshFailed
}
return result
}
result.Account = account
return result
}