feat(models): 模型目录与计费 - 按展示名分组并隔离管理员积分成本

This commit is contained in:
ddCat
2026-09-19 09:54:27 +08:00
parent 5ad05efb58
commit 237f2e2a6e
49 changed files with 802 additions and 276 deletions

View File

@@ -0,0 +1,19 @@
package model
// CreditCostPricing 仅供管理员配置和内部核算。持有者必须使用 json:"-",
// 管理端通过专用响应投影公开,禁止随用户目录、报价或账单序列化。
type CreditCostPricing struct {
Configured bool `json:"configured" gorm:"not null;default:false"`
UnitPriceMicrocredits int64 `json:"unitPriceMicrocredits" gorm:"not null;default:0"`
InputTokenPriceMicrocredits int64 `json:"inputTokenPriceMicrocredits" gorm:"not null;default:0"`
OutputTokenPriceMicrocredits int64 `json:"outputTokenPriceMicrocredits" gorm:"not null;default:0"`
CachedTokenPriceMicrocredits int64 `json:"cachedTokenPriceMicrocredits" gorm:"not null;default:0"`
}
// BillingCostSnapshot 独立于销售价格,切换供应线路时始终更新为实际执行线路的成本。
type BillingCostSnapshot struct {
CostPricing CreditCostPricing `json:"-" gorm:"embedded;embeddedPrefix:cost_"`
CostBillingMode string `json:"-" gorm:"size:32;not null;default:''"`
CostQuantity int64 `json:"-" gorm:"not null;default:0"`
CostVideoFormulaTokens int64 `json:"-" gorm:"not null;default:0"`
}

View File

@@ -1,26 +1,26 @@
package model
import (
"strings"
"time"
"gorm.io/gorm"
)
type ModelChannel struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36"`
Scope ChannelScope `json:"scope" gorm:"index;size:24"`
Enabled bool `json:"enabled" gorm:"index"`
Name string `json:"name" gorm:"size:80"`
PublicAlias string `json:"publicAlias" gorm:"size:80;not null;default:''"`
SortOrder int `json:"sortOrder" gorm:"not null;default:0"`
BaseURL string `json:"baseUrl"`
APIKey string `json:"-"`
SecretKey string `json:"-"`
APIFormat string `json:"apiFormat" gorm:"size:24"`
ConcurrencyLimit int `json:"concurrencyLimit"`
ModelsJSON string `json:"modelsJson" gorm:"type:text"`
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36"`
Scope ChannelScope `json:"scope" gorm:"index;size:24"`
Enabled bool `json:"enabled" gorm:"index"`
Name string `json:"name" gorm:"size:80"`
// PublicAlias 仅保留历史列供旧迁移读取,运行时不再使用渠道级前台名称。
PublicAlias string `json:"-" gorm:"size:80;not null;default:''"`
SortOrder int `json:"sortOrder" gorm:"not null;default:0"`
BaseURL string `json:"baseUrl"`
APIKey string `json:"-"`
SecretKey string `json:"-"`
APIFormat string `json:"apiFormat" gorm:"size:24"`
ConcurrencyLimit int `json:"concurrencyLimit"`
ModelsJSON string `json:"modelsJson" gorm:"type:text"`
// RetiredModelsJSON 记录已被一个模型家族吸收的上游 SKU,防止目录拉取时重新创建重复记录。
RetiredModelsJSON string `json:"-" gorm:"type:text"`
HeadersJSON string `json:"-" gorm:"type:text"`
@@ -29,13 +29,6 @@ type ModelChannel struct {
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
}
func (channel ModelChannel) PublicName() string {
if alias := strings.TrimSpace(channel.PublicAlias); alias != "" {
return alias
}
return channel.Name
}
type ChannelModel struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
ChannelID string `json:"channelId" gorm:"size:36;index;uniqueIndex:idx_channel_model_key_active,priority:1,where:deleted_at IS NULL"`
@@ -73,6 +66,7 @@ type ChannelModel struct {
// SelectorKey 是规范化 JSON,用于 PostgreSQL 的活动规格唯一约束。旧的 Resolution / VideoSeconds
// 仅保留给历史 API 与升级回填,不能再作为 SKU 唯一性依据。
type ChannelModelPriceTier struct {
CostPricing CreditCostPricing `json:"-" gorm:"embedded;embeddedPrefix:cost_"`
ID string `json:"id" gorm:"primaryKey;size:36"`
ChannelModelID string `json:"channelModelId" gorm:"size:36;index;uniqueIndex:idx_channel_model_price_tier_active,priority:1,where:deleted_at IS NULL"`
SelectorKey string `json:"selectorKey" gorm:"size:500;not null;default:{};uniqueIndex:idx_channel_model_price_tier_active,priority:2,where:deleted_at IS NULL"`
@@ -95,53 +89,55 @@ type ChannelModelPriceTier struct {
}
type ApiCallLog struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36;index:idx_api_logs_user_created,priority:1"`
TraceID string `json:"traceId,omitempty" gorm:"index;size:96"`
RequestID string `json:"requestId,omitempty" gorm:"index;size:96"`
UserDisplayName string `json:"userDisplayName,omitempty" gorm:"-"`
UserAccount string `json:"userAccount,omitempty" gorm:"-"`
ChannelID string `json:"channelId" gorm:"index;size:36;index:idx_api_logs_channel_created,priority:1"`
ChannelName string `json:"channelName" gorm:"-"`
TaskID string `json:"taskId,omitempty" gorm:"index;size:36"`
TaskStatus TaskStatus `json:"taskStatus,omitempty" gorm:"-"`
BillingOrderID string `json:"billingOrderId,omitempty" gorm:"index;size:36"`
BillingStatus BillingStatus `json:"billingStatus,omitempty" gorm:"-"`
BillingAmount int64 `json:"billingAmountMicrocredits" gorm:"-"`
BillingAvailable bool `json:"billingAvailable" gorm:"-"`
Source string `json:"source" gorm:"index;size:64"`
Capability string `json:"capability" gorm:"index;size:32"`
Operation string `json:"operation" gorm:"size:64"`
RequestKind string `json:"requestKind" gorm:"index;size:24"`
Billable bool `json:"billable" gorm:"index"`
APIFormat string `json:"apiFormat" gorm:"size:24"`
Method string `json:"method" gorm:"size:16"`
Path string `json:"path"`
Model string `json:"model" gorm:"size:120;index:idx_api_logs_model_created,priority:1"`
Status ApiCallStatus `json:"status" gorm:"index;size:24;index:idx_api_logs_status_created,priority:1"`
StatusCode int `json:"statusCode"`
DurationMs int64 `json:"durationMs"`
PollCount int `json:"pollCount"`
ProviderStatus string `json:"providerStatus,omitempty" gorm:"size:32"`
InputTokens int64 `json:"inputTokens"`
OutputTokens int64 `json:"outputTokens"`
CachedTokens int64 `json:"cachedTokens"`
UsageAvailable bool `json:"usageAvailable"`
MediaCount int `json:"mediaCount"`
MediaPreviewURL string `json:"mediaPreviewUrl,omitempty" gorm:"-"`
MediaPreviewKind string `json:"mediaPreviewKind,omitempty" gorm:"-"`
VideoSeconds int `json:"videoSeconds"`
ProviderRequestID string `json:"providerRequestId" gorm:"size:160"`
EstimatedCostMicros int64 `json:"estimatedCostMicros"`
CostAvailable bool `json:"costAvailable"`
Currency string `json:"currency" gorm:"size:12"`
ErrorCode string `json:"errorCode,omitempty" gorm:"index;size:80"`
Error string `json:"error"`
ConcurrencyLimit int `json:"concurrencyLimit"`
UpstreamURL string `json:"upstreamUrl"`
RequestContentType string `json:"requestContentType,omitempty" gorm:"size:160"`
RequestBody string `json:"requestBody,omitempty" gorm:"type:text"`
ResponseBody string `json:"responseBody,omitempty" gorm:"type:text"`
StartedAt time.Time `json:"startedAt"`
CreatedAt time.Time `json:"createdAt" gorm:"index;index:idx_api_logs_user_created,priority:2;index:idx_api_logs_channel_created,priority:2;index:idx_api_logs_model_created,priority:2;index:idx_api_logs_status_created,priority:2"`
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36;index:idx_api_logs_user_created,priority:1"`
TraceID string `json:"traceId,omitempty" gorm:"index;size:96"`
RequestID string `json:"requestId,omitempty" gorm:"index;size:96"`
UserDisplayName string `json:"userDisplayName,omitempty" gorm:"-"`
UserAccount string `json:"userAccount,omitempty" gorm:"-"`
ChannelID string `json:"channelId" gorm:"index;size:36;index:idx_api_logs_channel_created,priority:1"`
ChannelName string `json:"channelName" gorm:"-"`
TaskID string `json:"taskId,omitempty" gorm:"index;size:36"`
TaskStatus TaskStatus `json:"taskStatus,omitempty" gorm:"-"`
BillingOrderID string `json:"billingOrderId,omitempty" gorm:"index;size:36"`
BillingStatus BillingStatus `json:"billingStatus,omitempty" gorm:"-"`
BillingAmount int64 `json:"billingAmountMicrocredits" gorm:"-"`
BillingAvailable bool `json:"billingAvailable" gorm:"-"`
CreditCostConfigured bool `json:"creditCostConfigured" gorm:"-"`
CreditCostMicrocredits *int64 `json:"creditCostMicrocredits,omitempty" gorm:"-"`
Source string `json:"source" gorm:"index;size:64"`
Capability string `json:"capability" gorm:"index;size:32"`
Operation string `json:"operation" gorm:"size:64"`
RequestKind string `json:"requestKind" gorm:"index;size:24"`
Billable bool `json:"billable" gorm:"index"`
APIFormat string `json:"apiFormat" gorm:"size:24"`
Method string `json:"method" gorm:"size:16"`
Path string `json:"path"`
Model string `json:"model" gorm:"size:120;index:idx_api_logs_model_created,priority:1"`
Status ApiCallStatus `json:"status" gorm:"index;size:24;index:idx_api_logs_status_created,priority:1"`
StatusCode int `json:"statusCode"`
DurationMs int64 `json:"durationMs"`
PollCount int `json:"pollCount"`
ProviderStatus string `json:"providerStatus,omitempty" gorm:"size:32"`
InputTokens int64 `json:"inputTokens"`
OutputTokens int64 `json:"outputTokens"`
CachedTokens int64 `json:"cachedTokens"`
UsageAvailable bool `json:"usageAvailable"`
MediaCount int `json:"mediaCount"`
MediaPreviewURL string `json:"mediaPreviewUrl,omitempty" gorm:"-"`
MediaPreviewKind string `json:"mediaPreviewKind,omitempty" gorm:"-"`
VideoSeconds int `json:"videoSeconds"`
ProviderRequestID string `json:"providerRequestId" gorm:"size:160"`
EstimatedCostMicros int64 `json:"estimatedCostMicros"`
CostAvailable bool `json:"costAvailable"`
Currency string `json:"currency" gorm:"size:12"`
ErrorCode string `json:"errorCode,omitempty" gorm:"index;size:80"`
Error string `json:"error"`
ConcurrencyLimit int `json:"concurrencyLimit"`
UpstreamURL string `json:"upstreamUrl"`
RequestContentType string `json:"requestContentType,omitempty" gorm:"size:160"`
RequestBody string `json:"requestBody,omitempty" gorm:"type:text"`
ResponseBody string `json:"responseBody,omitempty" gorm:"type:text"`
StartedAt time.Time `json:"startedAt"`
CreatedAt time.Time `json:"createdAt" gorm:"index;index:idx_api_logs_user_created,priority:2;index:idx_api_logs_channel_created,priority:2;index:idx_api_logs_model_created,priority:2;index:idx_api_logs_status_created,priority:2"`
}

View File

@@ -33,12 +33,13 @@ type CreditLedgerEntry struct {
}
type BillingOrder struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"size:36;index;uniqueIndex:idx_billing_user_idempotency,priority:1"`
IdempotencyKey string `json:"idempotencyKey" gorm:"size:160;uniqueIndex:idx_billing_user_idempotency,priority:2"`
TaskID string `json:"taskId,omitempty" gorm:"index;size:36"`
ChannelID string `json:"channelId" gorm:"index;size:36"`
ChannelModelID string `json:"channelModelId" gorm:"index;size:36"`
BillingCostSnapshot `json:"-" gorm:"embedded"`
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"size:36;index;uniqueIndex:idx_billing_user_idempotency,priority:1"`
IdempotencyKey string `json:"idempotencyKey" gorm:"size:160;uniqueIndex:idx_billing_user_idempotency,priority:2"`
TaskID string `json:"taskId,omitempty" gorm:"index;size:36"`
ChannelID string `json:"channelId" gorm:"index;size:36"`
ChannelModelID string `json:"channelModelId" gorm:"index;size:36"`
// PriceTierID/Version 记录任务实际命中的规格档;金额字段仍是不可变结算快照。
PriceTierID string `json:"priceTierId,omitempty" gorm:"index;size:36"`
PriceTierVersion int64 `json:"priceTierVersion"`