package store import ( "crypto/sha256" "encoding/hex" "regexp" "strings" "time" ) // 角色 / 状态枚举 const ( RoleUser = "user" RoleAdmin = "admin" UserStatusActive = "active" UserStatusDisabled = "disabled" KeyStatusActive = "active" KeyStatusRevoked = "revoked" ChannelProviderOpenAI = "openai" ChannelProviderAnthropic = "anthropic" ChannelProviderCompatible = "compatible" ChannelHealthHealthy = "healthy" ChannelHealthDegraded = "degraded" ChannelHealthCooldown = "cooldown" FormatChat = "chat" FormatResponses = "responses" FormatMessages = "messages" UsageStatusSuccess = "success" UsageStatusError = "error" UsageStatusCanceled = "canceled" ) // User 用户 type User struct { ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"` Username string `gorm:"uniqueIndex;size:64;not null" json:"username"` Email string `gorm:"uniqueIndex;size:255;not null" json:"email"` PasswordHash string `gorm:"size:255;not null" json:"-"` Role string `gorm:"size:16;not null;default:user" json:"role"` Balance float64 `gorm:"type:numeric(20,8);not null;default:0" json:"balance"` Status string `gorm:"size:16;not null;default:active" json:"status"` AllowedModels []string `gorm:"type:jsonb;serializer:json" json:"allowed_models,omitempty"` DeniedModels []string `gorm:"type:jsonb;serializer:json" json:"denied_models,omitempty"` InviteCode *string `json:"invite_code,omitempty"` LastLoginAt *time.Time `json:"last_login_at,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // APIKey 密钥(SHA-256 hash 存储) type APIKey struct { ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"` UserID uint64 `gorm:"index;not null" json:"user_id"` Name string `gorm:"size:64;not null" json:"name"` KeyHash string `gorm:"uniqueIndex;size:64;not null" json:"-"` KeyPrefix string `gorm:"size:32;not null" json:"key_prefix"` QuotaTokensPerDay *int64 `json:"quota_tokens_per_day,omitempty"` QuotaRequestsPerDay *int `json:"quota_requests_per_day,omitempty"` AllowedModels []string `gorm:"type:jsonb;serializer:json" json:"allowed_models,omitempty"` ExpiresAt *time.Time `json:"expires_at,omitempty"` Status string `gorm:"size:16;not null;default:active" json:"status"` LastUsedAt *time.Time `json:"last_used_at,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // Channel 上游渠道 type Channel struct { ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"` Name string `gorm:"uniqueIndex;size:64;not null" json:"name"` Provider string `gorm:"size:16;not null" json:"provider"` Formats []string `gorm:"type:jsonb;serializer:json" json:"formats,omitempty"` BaseURL string `gorm:"size:255;not null" json:"base_url"` BaseURLs map[string]string `gorm:"type:jsonb;serializer:json" json:"base_urls,omitempty"` APIKeyEnc string `gorm:"size:1024;not null" json:"-"` Weight int `gorm:"not null;default:1" json:"weight"` Priority int `gorm:"not null;default:0" json:"priority"` TimeoutMS int `gorm:"not null;default:120000" json:"timeout_ms"` MaxConcurrency int `gorm:"not null;default:16" json:"max_concurrency"` HealthStatus string `gorm:"size:16;not null;default:healthy" json:"health_status"` Enabled bool `gorm:"not null;default:true" json:"enabled"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // FormatsEffective 返回渠道实际支持的原生协议 func (c *Channel) FormatsEffective() []string { if len(c.Formats) > 0 { return c.Formats } switch c.Provider { case ChannelProviderAnthropic: return []string{FormatMessages} case ChannelProviderOpenAI: return []string{FormatChat, FormatResponses} default: return []string{FormatChat} } } var versionSegRe = regexp.MustCompile(`/v[0-9]+/?$`) // UpstreamURL 按协议选 base_url,拼资源路径 func (c *Channel) UpstreamURL(proto, path string) string { base := c.BaseURL if len(c.BaseURLs) > 0 && c.BaseURLs[proto] != "" { base = c.BaseURLs[proto] } base = strings.TrimRight(base, "/") if base == "" { return path } if strings.HasSuffix(base, path) { return base } if versionSegRe.MatchString(base) { return base + path } return base + "/v1" + path } // Model 全局模型 + 定价(价格按每百万 token,USD) type Model struct { ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"` Name string `gorm:"uniqueIndex;size:128;not null" json:"name"` DisplayName string `gorm:"size:128" json:"display_name"` InputPrice float64 `gorm:"type:numeric(20,8);not null;default:0" json:"input_price"` OutputPrice float64 `gorm:"type:numeric(20,8);not null;default:0" json:"output_price"` CacheReadPrice float64 `gorm:"type:numeric(20,8);not null;default:0" json:"cache_read_price"` Enabled bool `gorm:"not null;default:true" json:"enabled"` Sort int `gorm:"not null;default:0" json:"sort"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // ChannelModelBinding 渠道↔模型绑定(多对多) type ChannelModelBinding struct { ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"` ChannelID uint64 `gorm:"index:idx_ch_model,unique;not null" json:"channel_id"` ModelID uint64 `gorm:"index:idx_ch_model,unique;not null" json:"model_id"` UpstreamModel string `gorm:"size:255;not null" json:"upstream_model"` Weight int `gorm:"not null;default:1" json:"weight"` Channel Channel `gorm:"foreignKey:ChannelID" json:"-"` Model Model `gorm:"foreignKey:ModelID" json:"-"` } // UsageLog 请求级用量明细 type UsageLog struct { ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"` RequestID string `gorm:"size:128" json:"request_id"` TraceID string `gorm:"size:64;index" json:"trace_id"` UserID uint64 `gorm:"index:idx_user_created;not null" json:"user_id"` KeyID uint64 `json:"key_id"` ChannelID uint64 `json:"channel_id"` ModelID uint64 `json:"model_id"` ModelName string `gorm:"size:128" json:"model_name"` Protocol string `gorm:"size:32" json:"protocol"` InputTokens int64 `json:"input_tokens"` OutputTokens int64 `json:"output_tokens"` CacheReadTokens int64 `json:"cache_read_tokens"` CacheCreationTokens int64 `json:"cache_creation_tokens"` InputPrice float64 `gorm:"type:numeric(20,8)" json:"input_price"` OutputPrice float64 `gorm:"type:numeric(20,8)" json:"output_price"` CacheReadPrice float64 `gorm:"type:numeric(20,8)" json:"cache_read_price"` Cost float64 `gorm:"type:numeric(20,8)" json:"cost"` LatencyMS int `json:"latency_ms"` Status string `gorm:"size:16;not null" json:"status"` ErrorCode *string `json:"error_code,omitempty"` CreatedAt time.Time `gorm:"index" json:"created_at"` } // UsageDaily 日粒度预聚合 type UsageDaily struct { ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"` UserID uint64 `gorm:"index:idx_daily_user_model,unique" json:"user_id"` ModelID uint64 `gorm:"index:idx_daily_user_model,unique" json:"model_id"` Date string `gorm:"size:10;index:idx_daily_user_model,unique" json:"date"` Requests int64 `json:"requests"` InputTokens int64 `json:"input_tokens"` OutputTokens int64 `json:"output_tokens"` CacheReadTokens int64 `json:"cache_read_tokens"` Cost float64 `gorm:"type:numeric(20,8)" json:"cost"` } // Passkey WebAuthn 凭据 type Passkey struct { ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"` UserID uint64 `gorm:"index;not null" json:"user_id"` Name string `gorm:"size:64" json:"name"` CredentialID string `gorm:"size:255;not null" json:"-"` PublicKey string `gorm:"size:512;not null" json:"-"` AttestationType string `gorm:"size:64" json:"-"` AAGUID string `gorm:"size:64" json:"-"` SignCount uint64 `json:"-"` DeviceType string `gorm:"size:255" json:"device_type,omitempty"` LastUsedAt int64 `json:"last_used_at,omitempty"` BackupEligible bool `json:"-"` BackupState bool `json:"-"` Transport string `gorm:"size:32" json:"-"` CreatedAt time.Time `json:"created_at"` } // SystemConfig 系统配置 type SystemConfig struct { Key string `gorm:"primaryKey;size:64" json:"key"` Value string `gorm:"type:jsonb;not null" json:"value"` } // AllModels 返回所有需要迁移的模型 func AllModels() []any { return []any{ &User{}, &APIKey{}, &Channel{}, &Model{}, &ChannelModelBinding{}, &UsageLog{}, &UsageDaily{}, &Passkey{}, &SystemConfig{}, } } // HashAPIKey hashes an API key using SHA-256 func HashAPIKey(key string) string { h := sha256.Sum256([]byte(key)) return hex.EncodeToString(h[:]) }