M0-M4: 推倒重来基线(基建+用户/密钥/核心代理+前端+管理后台+三协议互转)

- 后端 Go+Gin+GORM: 配置(OT_ env)/SQLite/Postgres 双驱动、用户体系(argon2id+JWT access/refresh)、
  API Key(sk- 48位, 仅存 SHA-256 哈希)
- 代理网关: /v1/chat/completions、/v1/responses、/v1/messages、/v1/models;错误按客户端协议返回
- 三协议互转(convert 包): Chat↔Messages↔Responses 请求/响应 + 流式 SSE 逐事件转换(直通优先)
- 用量计费: 异步批量记账、余额扣减、balance_logs、usage_daily 日聚合
- 管理 API: 用户/渠道 CRUD+测试+模型导入/模型定价+绑定/统计/系统配置
- 前端 Vue3+TS+Tailwind(taste-skill 设计 tokens): Landing/登录注册/控制台/管理后台,
  自建组件+Phosphor 图标+自建 SVG 趋势图, 已过 web-design-guidelines 复查
- mock 上游: OpenAI+Anthropic 双协议模拟(含流式)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sakurasan
2026-08-15 15:34:06 +08:00
co-authored by Claude
parent b25e9ec8a7
commit ec4de8d913
92 changed files with 6203 additions and 3064 deletions
+84 -86
View File
@@ -1,10 +1,8 @@
// Package store 数据模型与仓储层(GORM)。
// 字段设计对应 PLANNING.md §5:金额/价格 numeric(20,8),token bigint,时间 UTC。
// 字段设计对应 PLANNING.md §6:金额/价格 numeric(20,8),token bigint,时间 UTC。
package store
import (
"time"
)
import "time"
// 角色 / 状态枚举(字符串存库,便于阅读与迁移)
const (
@@ -17,20 +15,20 @@ const (
KeyStatusActive = "active"
KeyStatusRevoked = "revoked"
ChannelProviderOpenAI = "openai"
ChannelProviderAnthropic = "anthropic"
ChannelProviderCompatible = "compatible"
ChannelHealthHealthy = "healthy"
ChannelHealthDegraded = "degraded"
ChannelHealthCooldown = "cooldown"
ChannelProviderOpenAI = "openai"
ChannelProviderAnthropic = "anthropic"
ChannelProviderCompatible = "compatible"
ChannelHealthHealthy = "healthy"
ChannelHealthDegraded = "degraded"
ChannelHealthCooldown = "cooldown"
UsageStatusSuccess = "success"
UsageStatusError = "error"
UsageStatusCanceled = "canceled"
BalanceTypeRecharge = "recharge"
BalanceTypeUsage = "usage"
BalanceTypeRefund = "refund"
BalanceTypeRecharge = "recharge"
BalanceTypeUsage = "usage"
BalanceTypeRefund = "refund"
BalanceTypeAdminAdjust = "admin_adjust"
RechargeStatusPending = "pending"
@@ -40,7 +38,7 @@ const (
RechargeMethodOnline = "online"
)
// User 用户(PLANNING §5.1)
// User 用户(PLANNING §6.1)
type User struct {
ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"`
Username string `gorm:"uniqueIndex;size:64;not null" json:"username"`
@@ -55,7 +53,7 @@ type User struct {
UpdatedAt time.Time `json:"updated_at"`
}
// APIKey 密钥(PLANNING §5.2):库中只存 SHA-256 哈希 + 展示前缀
// 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"`
@@ -72,38 +70,38 @@ type APIKey struct {
UpdatedAt time.Time `json:"updated_at"`
}
// Channel 上游渠道(PLANNING §5.3)
// 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
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"`
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
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"`
}
// Model 全局模型 + 定价(PLANNING §5.4,价格按每百万 token,USD)
// 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"`
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 §5.4)
// 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"`
@@ -114,60 +112,60 @@ type ChannelModelBinding struct {
Model Model `gorm:"foreignKey:ModelID" json:"-"`
}
// UsageLog 请求级用量明细(PLANNING §5.5)
// 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"`
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 §5.6)
// 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"`
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"`
Cost float64 `gorm:"type:numeric(20,8)" json:"cost"`
}
// RechargeOrder 充值订单(PLANNING §5.7,预留:首版不做充值)
// 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"`
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 §5.8,幂等:ref_id + type 唯一)
// 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"`
@@ -179,7 +177,7 @@ type BalanceLog struct {
CreatedAt time.Time `json:"created_at"`
}
// SystemConfig 系统配置(PLANNING §5.9)
// SystemConfig 系统配置(PLANNING §6.9)
type SystemConfig struct {
Key string `gorm:"primaryKey;size:64" json:"key"`
Value string `gorm:"type:jsonb;not null" json:"value"`