refactor: move backend files to backend/ directory
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.
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
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[:])
|
||||
}
|
||||
Reference in New Issue
Block a user