Files
sub2api/backend/internal/service/account_scheduling_threshold_reason.go
T
李建琦 6d655c9903
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
2026-08-21 18:30:13 +08:00

189 lines
5.2 KiB
Go

package service
import (
"encoding/json"
"fmt"
"strings"
"time"
)
const AccountSchedulingThresholdReasonSource = "account_scheduling_threshold"
const (
defaultTempUnschedReasonErrorMessage = "temporary scheduling block reason unavailable"
defaultAccountSchedulingThresholdErrorMessage = "account scheduling threshold reached"
)
type tempUnschedReasonPayload struct {
Source string `json:"source,omitempty"`
Platform string `json:"platform,omitempty"`
Window string `json:"window,omitempty"`
Scope string `json:"scope,omitempty"`
ThresholdPercent int `json:"threshold_percent,omitempty"`
UsedPercent float64 `json:"used_percent,omitempty"`
UntilUnix int64 `json:"until_unix,omitempty"`
TriggeredAtUnix int64 `json:"triggered_at_unix,omitempty"`
ErrorMessage string `json:"error_message"`
}
type AccountSchedulingThresholdReasonInput struct {
Platform string
Window string
Scope string
ThresholdPercent int
UsedPercent float64
Until time.Time
Now time.Time
}
func BuildTempUnschedReasonPayload(source string, errorMessage string) string {
payload := tempUnschedReasonPayload{
Source: strings.TrimSpace(source),
ErrorMessage: normalizeTempUnschedReasonErrorMessage(errorMessage, defaultTempUnschedReasonErrorMessage),
}
raw, err := json.Marshal(payload)
if err != nil {
return payload.ErrorMessage
}
return string(raw)
}
func BuildAccountSchedulingThresholdReason(errorMessage string) string {
return BuildTempUnschedReasonPayload(
AccountSchedulingThresholdReasonSource,
normalizeTempUnschedReasonErrorMessage(errorMessage, defaultAccountSchedulingThresholdErrorMessage),
)
}
func BuildDetailedAccountSchedulingThresholdReason(input AccountSchedulingThresholdReasonInput) string {
triggeredAt := input.Now
if triggeredAt.IsZero() {
triggeredAt = time.Now().UTC()
}
payload := tempUnschedReasonPayload{
Source: AccountSchedulingThresholdReasonSource,
Platform: strings.TrimSpace(input.Platform),
Window: strings.TrimSpace(input.Window),
Scope: strings.TrimSpace(input.Scope),
ThresholdPercent: input.ThresholdPercent,
UsedPercent: input.UsedPercent,
TriggeredAtUnix: triggeredAt.Unix(),
ErrorMessage: buildAccountSchedulingThresholdErrorMessage(input),
}
if !input.Until.IsZero() {
payload.UntilUnix = input.Until.UTC().Unix()
}
raw, err := json.Marshal(payload)
if err != nil {
return payload.ErrorMessage
}
return string(raw)
}
func IsAccountSchedulingThresholdReason(rawReason string) bool {
payload, ok := parseTempUnschedReasonPayload(rawReason)
if !ok {
return false
}
return payload.Source == AccountSchedulingThresholdReasonSource
}
func parseTempUnschedReasonPayload(rawReason string) (tempUnschedReasonPayload, bool) {
rawReason = strings.TrimSpace(rawReason)
if rawReason == "" {
return tempUnschedReasonPayload{}, false
}
var payload tempUnschedReasonPayload
if err := json.Unmarshal([]byte(rawReason), &payload); err != nil {
return tempUnschedReasonPayload{}, false
}
payload.Source = strings.TrimSpace(payload.Source)
payload.ErrorMessage = strings.TrimSpace(payload.ErrorMessage)
return payload, true
}
func normalizeTempUnschedReasonErrorMessage(errorMessage string, fallback string) string {
errorMessage = strings.TrimSpace(errorMessage)
if errorMessage != "" {
return errorMessage
}
fallback = strings.TrimSpace(fallback)
if fallback != "" {
return fallback
}
return defaultTempUnschedReasonErrorMessage
}
func buildAccountSchedulingThresholdErrorMessage(input AccountSchedulingThresholdReasonInput) string {
platform := strings.TrimSpace(input.Platform)
if platform == "" {
platform = "account"
}
target := strings.TrimSpace(input.Window)
if scope := strings.TrimSpace(input.Scope); scope != "" {
if target == "" {
target = scope
} else {
target = target + "/" + scope
}
}
if target == "" {
target = "usage window"
}
threshold := input.ThresholdPercent
if threshold <= 0 {
threshold = 100
}
untilText := "the window reset"
if !input.Until.IsZero() {
untilText = input.Until.UTC().Format(time.RFC3339)
}
return fmt.Sprintf(
"%s scheduling threshold reached for %s: %.1f%% used >= %d%%; paused until %s",
platform,
target,
input.UsedPercent,
threshold,
untilText,
)
}
func tempUnschedStateFromStoredReason(rawReason string, fallbackUntilUnix int64) *TempUnschedState {
state := &TempUnschedState{
UntilUnix: fallbackUntilUnix,
RuleIndex: -1,
}
rawReason = strings.TrimSpace(rawReason)
if rawReason == "" {
state.ErrorMessage = defaultTempUnschedReasonErrorMessage
return state
}
parsed := TempUnschedState{RuleIndex: -1}
if err := json.Unmarshal([]byte(rawReason), &parsed); err == nil {
if fallbackUntilUnix > parsed.UntilUnix {
parsed.UntilUnix = fallbackUntilUnix
}
if strings.TrimSpace(parsed.ErrorMessage) == "" {
if IsAccountSchedulingThresholdReason(rawReason) {
parsed.ErrorMessage = defaultAccountSchedulingThresholdErrorMessage
} else {
parsed.ErrorMessage = rawReason
}
}
return &parsed
}
state.ErrorMessage = rawReason
return state
}