// Package store 数据模型与仓储层(GORM)。 // 字段设计对应 PLANNING.md §6:金额/价格 numeric(20,8),token bigint,时间 UTC。 package store import "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" // OpenAI Chat Completions FormatResponses = "responses" // OpenAI Responses API FormatMessages = "messages" // Anthropic Messages UsageStatusSuccess = "success" UsageStatusError = "error" UsageStatusCanceled = "canceled" BalanceTypeRecharge = "recharge" BalanceTypeUsage = "usage" BalanceTypeRefund = "refund" BalanceTypeAdminAdjust = "admin_adjust" RechargeStatusPending = "pending" RechargeStatusCredited = "credited" RechargeStatusRejected = "rejected" RechargeMethodManual = "manual" RechargeMethodOnline = "online" ) // User 用户(PLANNING §6.1) 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"` 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 密钥(PLANNING §6.2):库中只存 SHA-256 哈希 + 展示前缀 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 上游渠道(PLANNING §6.3) 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"` // openai|anthropic|compatible(供应商/默认格式) Formats []string `gorm:"type:jsonb;serializer:json" json:"formats,omitempty"` // 原生支持的协议格式 chat|responses|messages BaseURL string `gorm:"size:255;not null" json:"base_url"` APIKeyEnc string `gorm:"size:1024;not null" json:"-"` // AES-GCM 密文 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 返回渠道实际支持的原生协议;未显式配置时按 provider 推断。 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: // compatible return []string{FormatChat} } } // Model 全局模型 + 定价(PLANNING §6.4,价格按每百万 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 渠道↔模型绑定(多对多,PLANNING §6.4) 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 请求级用量明细(PLANNING §6.5) type UsageLog struct { ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"` RequestID string `gorm:"size:128" json:"request_id"` // 上游 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"` // responses|chat|messages 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 日粒度预聚合(PLANNING §6.6) 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"` // YYYY-MM-DD (UTC) 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"` } // RechargeOrder 充值订单(PLANNING §6.7,预留:首版不做充值) type RechargeOrder struct { ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"` UserID uint64 `gorm:"index;not null" json:"user_id"` Amount float64 `gorm:"type:numeric(20,8);not null" json:"amount"` Status string `gorm:"size:16;not null;default:pending" json:"status"` Method string `gorm:"size:16;not null;default:manual" json:"method"` TransactionID string `gorm:"size:128" json:"transaction_id,omitempty"` ReviewedBy *uint64 `json:"reviewed_by,omitempty"` ReviewedAt *time.Time `json:"reviewed_at,omitempty"` Remark string `gorm:"size:512" json:"remark,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // BalanceLog 余额流水(PLANNING §6.8,幂等:ref_id + type 唯一) type BalanceLog struct { ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"` UserID uint64 `gorm:"index:idx_balance_user;not null" json:"user_id"` Change float64 `gorm:"type:numeric(20,8);not null" json:"change"` BalanceAfter float64 `gorm:"type:numeric(20,8);not null" json:"balance_after"` Type string `gorm:"size:16;not null" json:"type"` RefID string `gorm:"size:128;index:idx_balance_ref,unique" json:"ref_id"` Remark string `gorm:"size:512" json:"remark,omitempty"` CreatedAt time.Time `json:"created_at"` } // SystemConfig 系统配置(PLANNING §6.9) 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{}, &RechargeOrder{}, &BalanceLog{}, &SystemConfig{}, } }