package controller import ( "encoding/json" "net/http" "one-api/model" "one-api/setting/ratio_setting" "one-api/dto" "sync" "time" "github.com/gin-gonic/gin" ) type upstreamResult struct { Name string `json:"name"` Data map[string]any `json:"data,omitempty"` Err string `json:"err,omitempty"` } type TestResult struct { Name string `json:"name"` Status string `json:"status"` Error string `json:"error,omitempty"` } type DifferenceItem struct { Current interface{} `json:"current"` // 当前本地值,可能为null Upstreams map[string]interface{} `json:"upstreams"` // 上游值:具体值/"same"/null } // SyncableChannel 可同步的渠道信息 type SyncableChannel struct { ID int `json:"id"` Name string `json:"name"` BaseURL string `json:"base_url"` Status int `json:"status"` } // FetchUpstreamRatios 后端并发拉取上游倍率 func FetchUpstreamRatios(c *gin.Context) { var req dto.UpstreamRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": err.Error()}) return } if req.Timeout <= 0 { req.Timeout = 10 } // build upstream list from ids + custom var upstreams []dto.UpstreamDTO if len(req.ChannelIDs) > 0 { // convert []int64 -> []int for model function intIds := make([]int, 0, len(req.ChannelIDs)) for _, id64 := range req.ChannelIDs { intIds = append(intIds, int(id64)) } dbChannels, _ := model.GetChannelsByIds(intIds) for _, ch := range dbChannels { upstreams = append(upstreams, dto.UpstreamDTO{ Name: ch.Name, BaseURL: ch.GetBaseURL(), Endpoint: "", // assume default endpoint }) } } var wg sync.WaitGroup ch := make(chan upstreamResult, len(upstreams)) for _, chn := range upstreams { wg.Add(1) go func(chItem dto.UpstreamDTO) { defer wg.Done() endpoint := chItem.Endpoint if endpoint == "" { endpoint = "/api/ratio_config" } url := chItem.BaseURL + endpoint client := http.Client{Timeout: time.Duration(req.Timeout) * time.Second} resp, err := client.Get(url) if err != nil { ch <- upstreamResult{Name: chItem.Name, Err: err.Error()} return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { ch <- upstreamResult{Name: chItem.Name, Err: resp.Status} return } var body struct { Success bool `json:"success"` Data map[string]any `json:"data"` Message string `json:"message"` } if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { ch <- upstreamResult{Name: chItem.Name, Err: err.Error()} return } if !body.Success { ch <- upstreamResult{Name: chItem.Name, Err: body.Message} return } ch <- upstreamResult{Name: chItem.Name, Data: body.Data} }(chn) } wg.Wait() close(ch) // 本地倍率配置 localData := ratio_setting.GetExposedData() var testResults []dto.TestResult var successfulChannels []struct { name string data map[string]any } for r := range ch { if r.Err != "" { testResults = append(testResults, dto.TestResult{ Name: r.Name, Status: "error", Error: r.Err, }) } else { testResults = append(testResults, dto.TestResult{ Name: r.Name, Status: "success", }) successfulChannels = append(successfulChannels, struct { name string data map[string]any }{name: r.Name, data: r.Data}) } } // 构建差异化数据 differences := buildDifferences(localData, successfulChannels) c.JSON(http.StatusOK, gin.H{ "success": true, "data": gin.H{ "differences": differences, "test_results": testResults, }, }) } // buildDifferences 构建差异化数据,只返回有意义的差异 func buildDifferences(localData map[string]any, successfulChannels []struct { name string data map[string]any }) map[string]map[string]dto.DifferenceItem { differences := make(map[string]map[string]dto.DifferenceItem) ratioTypes := []string{"model_ratio", "completion_ratio", "cache_ratio", "model_price"} // 收集所有模型名称 allModels := make(map[string]struct{}) // 从本地数据收集模型名称 for _, ratioType := range ratioTypes { if localRatioAny, ok := localData[ratioType]; ok { if localRatio, ok := localRatioAny.(map[string]float64); ok { for modelName := range localRatio { allModels[modelName] = struct{}{} } } } } // 从上游数据收集模型名称 for _, channel := range successfulChannels { for _, ratioType := range ratioTypes { if upstreamRatio, ok := channel.data[ratioType].(map[string]any); ok { for modelName := range upstreamRatio { allModels[modelName] = struct{}{} } } } } // 对每个模型和每个比率类型进行分析 for modelName := range allModels { for _, ratioType := range ratioTypes { // 获取本地值 var localValue interface{} = nil if localRatioAny, ok := localData[ratioType]; ok { if localRatio, ok := localRatioAny.(map[string]float64); ok { if val, exists := localRatio[modelName]; exists { localValue = val } } } // 收集上游值 upstreamValues := make(map[string]interface{}) hasUpstreamValue := false hasDifference := false for _, channel := range successfulChannels { var upstreamValue interface{} = nil if upstreamRatio, ok := channel.data[ratioType].(map[string]any); ok { if val, exists := upstreamRatio[modelName]; exists { upstreamValue = val hasUpstreamValue = true // 检查是否与本地值不同 if localValue != nil && localValue != val { hasDifference = true } else if localValue == val { upstreamValue = "same" } } } // 如果本地值为空但上游有值,这也是差异 if localValue == nil && upstreamValue != nil && upstreamValue != "same" { hasDifference = true } upstreamValues[channel.name] = upstreamValue } // 应用过滤逻辑 shouldInclude := false if localValue != nil { // 规则1: 本地值存在,至少有一个上游与本地值不同 if hasDifference { shouldInclude = true } // 规则2: 本地值存在,但所有上游都未设置 - 不包含 } else { // 规则3: 本地值不存在,至少有一个上游设置了值 if hasUpstreamValue { shouldInclude = true } } if shouldInclude { if differences[modelName] == nil { differences[modelName] = make(map[string]dto.DifferenceItem) } differences[modelName][ratioType] = dto.DifferenceItem{ Current: localValue, Upstreams: upstreamValues, } } } } return differences } // GetSyncableChannels 获取可用于倍率同步的渠道(base_url 不为空的渠道) func GetSyncableChannels(c *gin.Context) { channels, err := model.GetAllChannels(0, 0, true, false) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, "message": err.Error(), }) return } var syncableChannels []dto.SyncableChannel for _, channel := range channels { // 只返回 base_url 不为空的渠道 if channel.GetBaseURL() != "" { syncableChannels = append(syncableChannels, dto.SyncableChannel{ ID: channel.Id, Name: channel.Name, BaseURL: channel.GetBaseURL(), Status: channel.Status, }) } } c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", "data": syncableChannels, }) }