Reorganize project structure: - backend/cmd/openteam/ — entry point - backend/internal/ — core packages - backend/middleware/ — HTTP middleware - backend/router/ — route setup - backend/wire/ — dependency injection - backend/pkg/ — shared utilities - backend/go.mod, go.sum — Go module files Updated Makefile to work from backend/ directory. Removed old lowercase makefile.
234 lines
9.3 KiB
Go
234 lines
9.3 KiB
Go
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[:])
|
|
}
|