🚀 perf: optimize model management APIs, unify pricing types as array, and remove redundancies

Backend
- Add GetBoundChannelsByModelsMap to batch-fetch bound channels via a single JOIN (Distinct), compatible with SQLite/MySQL/PostgreSQL
- Replace per-record enrichment with a single-pass enrichModels to avoid N+1 queries; compute unions for prefix/suffix/contains matches in memory
- Change Model.QuotaType to QuotaTypes []int and expose quota_types in responses
- Add GetModelQuotaTypes for cached O(1) lookups; exact models return a single-element array
- Sort quota_types for stable output order
- Remove unused code: GetModelByName, GetBoundChannels, GetBoundChannelsForModels, FindModelByNameWithRule, buildPrefixes, buildSuffixes
- Clean up redundant comments, keeping concise and readable code

Frontend
- Models table: switch to quota_types, render multiple billing modes ([0], [1], [0,1], future values supported)
- Pricing table: switch to quota_types; ratio display now checks quota_types.includes(0); array rendering for billing tags

Compatibility
- SQL uses standard JOIN/IN/Distinct; works across SQLite/MySQL/PostgreSQL
- Lint passes; no DB schema changes (quota_types is a JSON response field only)

Breaking Change
- API field renamed: quota_type -> quota_types (array). Update clients accordingly.
This commit is contained in:
t0ng7u
2025-08-11 14:40:01 +08:00
parent e64b13c925
commit 4ad8eefaec
5 changed files with 239 additions and 254 deletions

View File

@@ -2,6 +2,7 @@ package controller
import (
"encoding/json"
"sort"
"strconv"
"strings"
@@ -21,10 +22,8 @@ func GetAllModelsMeta(c *gin.Context) {
common.ApiError(c, err)
return
}
// 填充附加字段
for _, m := range modelsMeta {
fillModelExtra(m)
}
// 批量填充附加字段,提升列表接口性能
enrichModels(modelsMeta)
var total int64
model.DB.Model(&model.Model{}).Count(&total)
@@ -54,9 +53,8 @@ func SearchModelsMeta(c *gin.Context) {
common.ApiError(c, err)
return
}
for _, m := range modelsMeta {
fillModelExtra(m)
}
// 批量填充附加字段,提升列表接口性能
enrichModels(modelsMeta)
pageInfo.SetTotal(int(total))
pageInfo.SetItems(modelsMeta)
common.ApiSuccess(c, pageInfo)
@@ -75,7 +73,7 @@ func GetModelMeta(c *gin.Context) {
common.ApiError(c, err)
return
}
fillModelExtra(&m)
enrichModels([]*model.Model{&m})
common.ApiSuccess(c, &m)
}
@@ -162,104 +160,157 @@ func DeleteModelMeta(c *gin.Context) {
common.ApiSuccess(c, nil)
}
// 辅助函数:填充 Endpoints 和 BoundChannels 和 EnableGroups
func fillModelExtra(m *model.Model) {
// 若为精确匹配,保持原有逻辑
if m.NameRule == model.NameRuleExact {
if m.Endpoints == "" {
eps := model.GetModelSupportEndpointTypes(m.ModelName)
if b, err := json.Marshal(eps); err == nil {
m.Endpoints = string(b)
}
}
if channels, err := model.GetBoundChannels(m.ModelName); err == nil {
m.BoundChannels = channels
}
m.EnableGroups = model.GetModelEnableGroups(m.ModelName)
m.QuotaType = model.GetModelQuotaType(m.ModelName)
// enrichModels 批量填充附加信息:端点、渠道、分组、计费类型,避免 N+1 查询
func enrichModels(models []*model.Model) {
if len(models) == 0 {
return
}
// 非精确匹配:计算并集
pricings := model.GetPricing()
// 匹配到的模型名称集合
matchedNames := make([]string, 0)
// 端点去重集合
endpointSet := make(map[constant.EndpointType]struct{})
// 已绑定渠道去重集合
channelSet := make(map[string]model.BoundChannel)
// 分组去重集合
groupSet := make(map[string]struct{})
// 计费类型(若有任意模型为 1则返回 1
quotaTypeSet := make(map[int]struct{})
for _, p := range pricings {
var matched bool
switch m.NameRule {
case model.NameRulePrefix:
matched = strings.HasPrefix(p.ModelName, m.ModelName)
case model.NameRuleSuffix:
matched = strings.HasSuffix(p.ModelName, m.ModelName)
case model.NameRuleContains:
matched = strings.Contains(p.ModelName, m.ModelName)
}
if !matched {
// 1) 拆分精确与规则匹配
exactNames := make([]string, 0)
exactIdx := make(map[string][]int) // modelName -> indices in models
ruleIndices := make([]int, 0)
for i, m := range models {
if m == nil {
continue
}
// 记录匹配到的模型名称
matchedNames = append(matchedNames, p.ModelName)
// 收集端点
for _, et := range p.SupportedEndpointTypes {
endpointSet[et] = struct{}{}
}
// 收集分组
for _, g := range p.EnableGroup {
groupSet[g] = struct{}{}
}
// 收集计费类型
quotaTypeSet[p.QuotaType] = struct{}{}
}
// 序列化端点
if len(endpointSet) > 0 && m.Endpoints == "" {
eps := make([]constant.EndpointType, 0, len(endpointSet))
for et := range endpointSet {
eps = append(eps, et)
}
if b, err := json.Marshal(eps); err == nil {
m.Endpoints = string(b)
if m.NameRule == model.NameRuleExact {
exactNames = append(exactNames, m.ModelName)
exactIdx[m.ModelName] = append(exactIdx[m.ModelName], i)
} else {
ruleIndices = append(ruleIndices, i)
}
}
// 序列化分组
if len(groupSet) > 0 {
groups := make([]string, 0, len(groupSet))
for g := range groupSet {
groups = append(groups, g)
// 2) 批量查询精确模型的绑定渠道
channelsByModel, _ := model.GetBoundChannelsByModelsMap(exactNames)
// 3) 精确模型:端点从缓存、渠道批量映射、分组/计费类型从缓存
for name, indices := range exactIdx {
chs := channelsByModel[name]
for _, idx := range indices {
mm := models[idx]
if mm.Endpoints == "" {
eps := model.GetModelSupportEndpointTypes(mm.ModelName)
if b, err := json.Marshal(eps); err == nil {
mm.Endpoints = string(b)
}
}
mm.BoundChannels = chs
mm.EnableGroups = model.GetModelEnableGroups(mm.ModelName)
mm.QuotaTypes = model.GetModelQuotaTypes(mm.ModelName)
}
m.EnableGroups = groups
}
// 确定计费类型:仅当所有匹配模型计费类型一致时才返回该类型,否则返回 -1 表示未知/不确定
if len(quotaTypeSet) == 1 {
for k := range quotaTypeSet {
m.QuotaType = k
}
} else {
m.QuotaType = -1
if len(ruleIndices) == 0 {
return
}
// 批量查询并序列化渠道
if len(matchedNames) > 0 {
if channels, err := model.GetBoundChannelsForModels(matchedNames); err == nil {
for _, ch := range channels {
// 4) 一次性读取定价缓存,内存匹配所有规则模型
pricings := model.GetPricing()
// 为全部规则模型收集匹配名集合、端点并集、分组并集、配额集合
matchedNamesByIdx := make(map[int][]string)
endpointSetByIdx := make(map[int]map[constant.EndpointType]struct{})
groupSetByIdx := make(map[int]map[string]struct{})
quotaSetByIdx := make(map[int]map[int]struct{})
for _, p := range pricings {
for _, idx := range ruleIndices {
mm := models[idx]
var matched bool
switch mm.NameRule {
case model.NameRulePrefix:
matched = strings.HasPrefix(p.ModelName, mm.ModelName)
case model.NameRuleSuffix:
matched = strings.HasSuffix(p.ModelName, mm.ModelName)
case model.NameRuleContains:
matched = strings.Contains(p.ModelName, mm.ModelName)
}
if !matched {
continue
}
matchedNamesByIdx[idx] = append(matchedNamesByIdx[idx], p.ModelName)
es := endpointSetByIdx[idx]
if es == nil {
es = make(map[constant.EndpointType]struct{})
endpointSetByIdx[idx] = es
}
for _, et := range p.SupportedEndpointTypes {
es[et] = struct{}{}
}
gs := groupSetByIdx[idx]
if gs == nil {
gs = make(map[string]struct{})
groupSetByIdx[idx] = gs
}
for _, g := range p.EnableGroup {
gs[g] = struct{}{}
}
qs := quotaSetByIdx[idx]
if qs == nil {
qs = make(map[int]struct{})
quotaSetByIdx[idx] = qs
}
qs[p.QuotaType] = struct{}{}
}
}
// 5) 汇总所有匹配到的模型名称,批量查询一次渠道
allMatchedSet := make(map[string]struct{})
for _, names := range matchedNamesByIdx {
for _, n := range names {
allMatchedSet[n] = struct{}{}
}
}
allMatched := make([]string, 0, len(allMatchedSet))
for n := range allMatchedSet {
allMatched = append(allMatched, n)
}
matchedChannelsByModel, _ := model.GetBoundChannelsByModelsMap(allMatched)
// 6) 回填每个规则模型的并集信息
for _, idx := range ruleIndices {
mm := models[idx]
// 端点并集 -> 序列化
if es, ok := endpointSetByIdx[idx]; ok && mm.Endpoints == "" {
eps := make([]constant.EndpointType, 0, len(es))
for et := range es {
eps = append(eps, et)
}
if b, err := json.Marshal(eps); err == nil {
mm.Endpoints = string(b)
}
}
// 分组并集
if gs, ok := groupSetByIdx[idx]; ok {
groups := make([]string, 0, len(gs))
for g := range gs {
groups = append(groups, g)
}
mm.EnableGroups = groups
}
// 配额类型集合(保持去重并排序)
if qs, ok := quotaSetByIdx[idx]; ok {
arr := make([]int, 0, len(qs))
for k := range qs {
arr = append(arr, k)
}
sort.Ints(arr)
mm.QuotaTypes = arr
}
// 渠道并集
names := matchedNamesByIdx[idx]
channelSet := make(map[string]model.BoundChannel)
for _, n := range names {
for _, ch := range matchedChannelsByModel[n] {
key := ch.Name + "_" + strconv.Itoa(ch.Type)
channelSet[key] = ch
}
@@ -269,11 +320,11 @@ func fillModelExtra(m *model.Model) {
for _, ch := range channelSet {
chs = append(chs, ch)
}
m.BoundChannels = chs
mm.BoundChannels = chs
}
}
// 设置匹配信息
m.MatchedModels = matchedNames
m.MatchedCount = len(matchedNames)
// 匹配信息
mm.MatchedModels = names
mm.MatchedCount = len(names)
}
}