2025-12-26 15:40:24 +08:00
|
|
|
|
package service
|
|
|
|
|
|
|
2026-02-28 15:01:20 +08:00
|
|
|
|
import (
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
)
|
2025-12-26 15:40:24 +08:00
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
BillingTypeBalance int8 = 0 // 钱包余额
|
|
|
|
|
|
BillingTypeSubscription int8 = 1 // 订阅套餐
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-28 15:01:20 +08:00
|
|
|
|
type RequestType int16
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
RequestTypeUnknown RequestType = 0
|
|
|
|
|
|
RequestTypeSync RequestType = 1
|
|
|
|
|
|
RequestTypeStream RequestType = 2
|
|
|
|
|
|
RequestTypeWSV2 RequestType = 3
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func (t RequestType) IsValid() bool {
|
|
|
|
|
|
switch t {
|
|
|
|
|
|
case RequestTypeUnknown, RequestTypeSync, RequestTypeStream, RequestTypeWSV2:
|
|
|
|
|
|
return true
|
|
|
|
|
|
default:
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (t RequestType) Normalize() RequestType {
|
|
|
|
|
|
if t.IsValid() {
|
|
|
|
|
|
return t
|
|
|
|
|
|
}
|
|
|
|
|
|
return RequestTypeUnknown
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (t RequestType) String() string {
|
|
|
|
|
|
switch t.Normalize() {
|
|
|
|
|
|
case RequestTypeSync:
|
|
|
|
|
|
return "sync"
|
|
|
|
|
|
case RequestTypeStream:
|
|
|
|
|
|
return "stream"
|
|
|
|
|
|
case RequestTypeWSV2:
|
|
|
|
|
|
return "ws_v2"
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "unknown"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func RequestTypeFromInt16(v int16) RequestType {
|
|
|
|
|
|
return RequestType(v).Normalize()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func ParseUsageRequestType(value string) (RequestType, error) {
|
|
|
|
|
|
switch strings.ToLower(strings.TrimSpace(value)) {
|
|
|
|
|
|
case "unknown":
|
|
|
|
|
|
return RequestTypeUnknown, nil
|
|
|
|
|
|
case "sync":
|
|
|
|
|
|
return RequestTypeSync, nil
|
|
|
|
|
|
case "stream":
|
|
|
|
|
|
return RequestTypeStream, nil
|
|
|
|
|
|
case "ws_v2":
|
|
|
|
|
|
return RequestTypeWSV2, nil
|
|
|
|
|
|
default:
|
|
|
|
|
|
return RequestTypeUnknown, fmt.Errorf("invalid request_type, allowed values: unknown, sync, stream, ws_v2")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func RequestTypeFromLegacy(stream bool, openAIWSMode bool) RequestType {
|
|
|
|
|
|
if openAIWSMode {
|
|
|
|
|
|
return RequestTypeWSV2
|
|
|
|
|
|
}
|
|
|
|
|
|
if stream {
|
|
|
|
|
|
return RequestTypeStream
|
|
|
|
|
|
}
|
|
|
|
|
|
return RequestTypeSync
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func ApplyLegacyRequestFields(requestType RequestType, fallbackStream bool, fallbackOpenAIWSMode bool) (stream bool, openAIWSMode bool) {
|
|
|
|
|
|
switch requestType.Normalize() {
|
|
|
|
|
|
case RequestTypeSync:
|
|
|
|
|
|
return false, false
|
|
|
|
|
|
case RequestTypeStream:
|
|
|
|
|
|
return true, false
|
|
|
|
|
|
case RequestTypeWSV2:
|
|
|
|
|
|
return true, true
|
|
|
|
|
|
default:
|
|
|
|
|
|
return fallbackStream, fallbackOpenAIWSMode
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 15:40:24 +08:00
|
|
|
|
type UsageLog struct {
|
|
|
|
|
|
ID int64
|
|
|
|
|
|
UserID int64
|
2026-01-04 19:27:53 +08:00
|
|
|
|
APIKeyID int64
|
2025-12-26 15:40:24 +08:00
|
|
|
|
AccountID int64
|
|
|
|
|
|
RequestID string
|
|
|
|
|
|
Model string
|
2026-03-08 23:22:28 +08:00
|
|
|
|
// ServiceTier records the OpenAI service tier used for billing, e.g. "priority" / "flex".
|
|
|
|
|
|
ServiceTier *string
|
2026-02-03 14:36:29 +08:00
|
|
|
|
// ReasoningEffort is the request's reasoning effort level (OpenAI Responses API),
|
|
|
|
|
|
// e.g. "low" / "medium" / "high" / "xhigh". Nil means not provided / not applicable.
|
|
|
|
|
|
ReasoningEffort *string
|
2025-12-26 15:40:24 +08:00
|
|
|
|
|
|
|
|
|
|
GroupID *int64
|
|
|
|
|
|
SubscriptionID *int64
|
|
|
|
|
|
|
|
|
|
|
|
InputTokens int
|
|
|
|
|
|
OutputTokens int
|
|
|
|
|
|
CacheCreationTokens int
|
|
|
|
|
|
CacheReadTokens int
|
|
|
|
|
|
|
2026-02-14 18:15:35 +08:00
|
|
|
|
CacheCreation5mTokens int `gorm:"column:cache_creation_5m_tokens"`
|
|
|
|
|
|
CacheCreation1hTokens int `gorm:"column:cache_creation_1h_tokens"`
|
2025-12-26 15:40:24 +08:00
|
|
|
|
|
|
|
|
|
|
InputCost float64
|
|
|
|
|
|
OutputCost float64
|
|
|
|
|
|
CacheCreationCost float64
|
|
|
|
|
|
CacheReadCost float64
|
|
|
|
|
|
TotalCost float64
|
|
|
|
|
|
ActualCost float64
|
|
|
|
|
|
RateMultiplier float64
|
2026-01-14 16:12:08 +08:00
|
|
|
|
// AccountRateMultiplier 账号计费倍率快照(nil 表示历史数据,按 1.0 处理)
|
|
|
|
|
|
AccountRateMultiplier *float64
|
2025-12-26 15:40:24 +08:00
|
|
|
|
|
|
|
|
|
|
BillingType int8
|
2026-02-28 15:01:20 +08:00
|
|
|
|
RequestType RequestType
|
2025-12-26 15:40:24 +08:00
|
|
|
|
Stream bool
|
2026-02-28 15:01:20 +08:00
|
|
|
|
OpenAIWSMode bool
|
2025-12-26 15:40:24 +08:00
|
|
|
|
DurationMs *int
|
|
|
|
|
|
FirstTokenMs *int
|
2026-01-06 16:23:56 +08:00
|
|
|
|
UserAgent *string
|
2026-01-09 21:59:32 +08:00
|
|
|
|
IPAddress *string
|
2025-12-26 15:40:24 +08:00
|
|
|
|
|
2026-02-17 11:22:08 +03:00
|
|
|
|
// Cache TTL Override 标记(管理员强制替换了缓存 TTL 计费)
|
|
|
|
|
|
CacheTTLOverridden bool
|
|
|
|
|
|
|
2026-01-05 17:07:29 +08:00
|
|
|
|
// 图片生成字段
|
|
|
|
|
|
ImageCount int
|
|
|
|
|
|
ImageSize *string
|
2026-01-31 20:22:22 +08:00
|
|
|
|
MediaType *string
|
2026-01-05 17:07:29 +08:00
|
|
|
|
|
2025-12-26 15:40:24 +08:00
|
|
|
|
CreatedAt time.Time
|
|
|
|
|
|
|
|
|
|
|
|
User *User
|
2026-01-04 19:27:53 +08:00
|
|
|
|
APIKey *APIKey
|
2025-12-26 15:40:24 +08:00
|
|
|
|
Account *Account
|
|
|
|
|
|
Group *Group
|
|
|
|
|
|
Subscription *UserSubscription
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (u *UsageLog) TotalTokens() int {
|
|
|
|
|
|
return u.InputTokens + u.OutputTokens + u.CacheCreationTokens + u.CacheReadTokens
|
|
|
|
|
|
}
|
2026-02-28 15:01:20 +08:00
|
|
|
|
|
|
|
|
|
|
func (u *UsageLog) EffectiveRequestType() RequestType {
|
|
|
|
|
|
if u == nil {
|
|
|
|
|
|
return RequestTypeUnknown
|
|
|
|
|
|
}
|
|
|
|
|
|
if normalized := u.RequestType.Normalize(); normalized != RequestTypeUnknown {
|
|
|
|
|
|
return normalized
|
|
|
|
|
|
}
|
|
|
|
|
|
return RequestTypeFromLegacy(u.Stream, u.OpenAIWSMode)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (u *UsageLog) SyncRequestTypeAndLegacyFields() {
|
|
|
|
|
|
if u == nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
requestType := u.EffectiveRequestType()
|
|
|
|
|
|
u.RequestType = requestType
|
|
|
|
|
|
u.Stream, u.OpenAIWSMode = ApplyLegacyRequestFields(requestType, u.Stream, u.OpenAIWSMode)
|
|
|
|
|
|
}
|