feat(channel): 缓存扁平化 + 网关映射集成 + 计费模式统一 + 模型限制

- 缓存重构为 O(1) 哈希结构 (pricingByGroupModel, mappingByGroupModel)
- 渠道模型映射接入网关流程 (Forward 前应用, a→b→c 映射链)
- 新增 billing_model_source 配置 (请求模型/最终模型计费)
- usage_logs 新增 channel_id, model_mapping_chain, billing_tier 字段
- 每种计费模式统一支持默认价格 + 区间定价
- 渠道模型限制开关 (restrict_models)
- 分组按平台分类展示 + 彩色图标
- 必填字段红色星号 + 模型映射 UI
- 去除模型通配符支持
This commit is contained in:
erio
2026-03-30 13:26:05 +08:00
parent 29d58f2414
commit ebac0dc628
23 changed files with 779 additions and 206 deletions

View File

@@ -23,14 +23,21 @@ func (m BillingMode) IsValid() bool {
return false
}
const (
BillingModelSourceRequested = "requested"
BillingModelSourceUpstream = "upstream"
)
// Channel 渠道实体
type Channel struct {
ID int64
Name string
Description string
Status string
CreatedAt time.Time
UpdatedAt time.Time
ID int64
Name string
Description string
Status string
BillingModelSource string // "requested" or "upstream"
RestrictModels bool // 是否限制模型(仅允许定价列表中的模型)
CreatedAt time.Time
UpdatedAt time.Time
// 关联的分组 ID 列表
GroupIDs []int64
@@ -44,13 +51,14 @@ type Channel struct {
type ChannelModelPricing struct {
ID int64
ChannelID int64
Models []string // 绑定的模型列表
BillingMode BillingMode // 计费模式
InputPrice *float64 // 每 token 输入价格USD— 向后兼容 flat 定价
OutputPrice *float64 // 每 token 输出价格USD
CacheWritePrice *float64 // 缓存写入价格
CacheReadPrice *float64 // 缓存读取价格
ImageOutputPrice *float64 // 图片输出价格(向后兼容)
Models []string // 绑定的模型列表
BillingMode BillingMode // 计费模式
InputPrice *float64 // 每 token 输入价格USD— 向后兼容 flat 定价
OutputPrice *float64 // 每 token 输出价格USD
CacheWritePrice *float64 // 缓存写入价格
CacheReadPrice *float64 // 缓存读取价格
ImageOutputPrice *float64 // 图片输出价格(向后兼容)
PerRequestPrice *float64 // 默认按次计费价格USD
Intervals []PricingInterval // 区间定价列表
CreatedAt time.Time
UpdatedAt time.Time
@@ -106,12 +114,10 @@ func (c *Channel) IsActive() bool {
}
// GetModelPricing 根据模型名查找渠道定价,未找到返回 nil。
// 优先精确匹配,然后通配符匹配(如 claude-opus-*)。大小写不敏感。
// 返回值拷贝,不污染缓存。
// 精确匹配,大小写不敏感。返回值拷贝,不污染缓存
func (c *Channel) GetModelPricing(model string) *ChannelModelPricing {
modelLower := strings.ToLower(model)
// 第一轮:精确匹配
for i := range c.ModelPricing {
for _, m := range c.ModelPricing[i].Models {
if strings.ToLower(m) == modelLower {
@@ -121,20 +127,6 @@ func (c *Channel) GetModelPricing(model string) *ChannelModelPricing {
}
}
// 第二轮:通配符匹配(仅支持末尾 *
for i := range c.ModelPricing {
for _, m := range c.ModelPricing[i].Models {
mLower := strings.ToLower(m)
if strings.HasSuffix(mLower, "*") {
prefix := strings.TrimSuffix(mLower, "*")
if strings.HasPrefix(modelLower, prefix) {
cp := c.ModelPricing[i].Clone()
return &cp
}
}
}
}
return nil
}