From 195be56c46612520fb74988e71684caf05806407 Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Sun, 10 Aug 2025 23:11:35 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8F=8E=EF=B8=8F=20perf:=20optimize=20aggr?= =?UTF-8?q?egated=20model=20look-ups=20by=20batching=20bound-channel=20que?= =?UTF-8?q?ries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary ------- 1. **Backend** • `model/model_meta.go` – Add `GetBoundChannelsForModels([]string)` to retrieve channels for multiple models in a single SQL (`IN (?)`) and deduplicate with `GROUP BY`. • `controller/model_meta.go` – In non-exact `fillModelExtra`: – Remove per-model `GetBoundChannels` calls. – Collect matched model names, then call `GetBoundChannelsForModels` once and merge results into `channelSet`. – Minor cleanup on loop logic; channel aggregation now happens after quota/group/endpoint processing. Impact ------ • Eliminates N+1 query pattern for prefix/suffix/contains rules. • Reduces DB round-trips from *N + 1* to **1**, markedly speeding up the model-management list load. • Keeps existing `GetBoundChannels` API intact for single-model scenarios; no breaking changes. --- controller/model_meta.go | 35 ++++++++++++++++++----------------- model/model_meta.go | 15 +++++++++++++++ 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/controller/model_meta.go b/controller/model_meta.go index e7d29fcf..9b9f7849 100644 --- a/controller/model_meta.go +++ b/controller/model_meta.go @@ -188,6 +188,7 @@ func fillModelExtra(m *model.Model) { // 端点去重集合 endpointSet := make(map[constant.EndpointType]struct{}) + // 已绑定渠道去重集合 channelSet := make(map[string]model.BoundChannel) // 分组去重集合 @@ -224,14 +225,6 @@ func fillModelExtra(m *model.Model) { // 收集计费类型 quotaTypeSet[p.QuotaType] = struct{}{} - - // 收集渠道 - if channels, err := model.GetBoundChannels(p.ModelName); err == nil { - for _, ch := range channels { - key := ch.Name + "_" + strconv.Itoa(ch.Type) - channelSet[key] = ch - } - } } // 序列化端点 @@ -245,15 +238,6 @@ func fillModelExtra(m *model.Model) { } } - // 序列化渠道 - if len(channelSet) > 0 { - channels := make([]model.BoundChannel, 0, len(channelSet)) - for _, ch := range channelSet { - channels = append(channels, ch) - } - m.BoundChannels = channels - } - // 序列化分组 if len(groupSet) > 0 { groups := make([]string, 0, len(groupSet)) @@ -272,6 +256,23 @@ func fillModelExtra(m *model.Model) { m.QuotaType = -1 } + // 批量查询并序列化渠道 + if len(matchedNames) > 0 { + if channels, err := model.GetBoundChannelsForModels(matchedNames); err == nil { + for _, ch := range channels { + key := ch.Name + "_" + strconv.Itoa(ch.Type) + channelSet[key] = ch + } + } + if len(channelSet) > 0 { + chs := make([]model.BoundChannel, 0, len(channelSet)) + for _, ch := range channelSet { + chs = append(chs, ch) + } + m.BoundChannels = chs + } + } + // 设置匹配信息 m.MatchedModels = matchedNames m.MatchedCount = len(matchedNames) diff --git a/model/model_meta.go b/model/model_meta.go index d7bc78c6..552c3c27 100644 --- a/model/model_meta.go +++ b/model/model_meta.go @@ -139,6 +139,21 @@ func GetBoundChannels(modelName string) ([]BoundChannel, error) { return channels, err } +// GetBoundChannelsForModels 批量查询多模型的绑定渠道并去重返回 +func GetBoundChannelsForModels(modelNames []string) ([]BoundChannel, error) { + if len(modelNames) == 0 { + return make([]BoundChannel, 0), nil + } + var channels []BoundChannel + err := DB.Table("channels"). + Select("channels.name, channels.type"). + Joins("join abilities on abilities.channel_id = channels.id"). + Where("abilities.model IN ? AND abilities.enabled = ?", modelNames, true). + Group("channels.id"). + Scan(&channels).Error + return channels, err +} + // FindModelByNameWithRule 根据模型名称和匹配规则查找模型元数据,优先级:精确 > 前缀 > 后缀 > 包含 func FindModelByNameWithRule(name string) (*Model, error) { // 1. 精确匹配