package controller import ( "net/http" "os" "strconv" "strings" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/constant" "github.com/QuantumNous/new-api/model" "github.com/QuantumNous/new-api/setting/ratio_setting" "github.com/gin-gonic/gin" ) // coalesceStr 返回第一个非空字符串,若均为空则返回空串。 func coalesceStr(vals ...string) string { for _, v := range vals { if strings.TrimSpace(v) != "" { return strings.TrimSpace(v) } } return "" } // tfOpenSyncExportRow 仅用于跨站同步导出,不包含渠道密钥。 type tfOpenSyncExportRow struct { ID int `json:"id"` Name string `json:"name"` Models string `json:"models"` Group string `json:"group"` Status int `json:"status"` Type int `json:"type"` ChannelNo string `json:"channel_no"` SupplierApplicationID int `json:"supplier_application_id"` SupplierAlias string `json:"supplier_alias,omitempty"` SupplierType string `json:"supplier_type,omitempty"` CompanyLogoURL string `json:"company_logo_url,omitempty"` PriceDiscountPercent *float64 `json:"price_discount_percent,omitempty"` MarkupDiscountRate *float64 `json:"markup_discount_rate,omitempty"` ModelMapping string `json:"model_mapping,omitempty"` ModelPrice map[string]float64 `json:"model_price,omitempty"` ModelRatio map[string]float64 `json:"model_ratio,omitempty"` } func authorizeTFOpenSyncExport(c *gin.Context) bool { secretEnv := strings.TrimSpace(os.Getenv("TOKENFACTORY_OPEN_SYNC_SECRET")) hdr := strings.TrimSpace(c.GetHeader("X-TokenFactory-Open-Sync-Secret")) if secretEnv != "" && hdr != "" && hdr == secretEnv { return true } auth := strings.TrimSpace(c.GetHeader("Authorization")) if auth == "" { return false } if strings.HasPrefix(strings.ToLower(auth), "bearer ") { auth = strings.TrimSpace(auth[7:]) } // 优先支持普通 API 令牌(sk- 前缀),方便上游发放非管理员同步 key。 tokenKey := strings.TrimPrefix(auth, "sk-") if tokenKey != "" { if _, err := model.ValidateUserToken(tokenKey); err == nil { return true } } // 兼容 access token(不再强制管理员角色)。 return model.ValidateAccessToken(auth) != nil } // TFOpenSyncExportChannels 供子站 TokenFactoryOpen 同步:返回全站渠道(脱敏)及渠道级定价/倍率。 // 鉴权:环境变量 TOKENFACTORY_OPEN_SYNC_SECRET + 请求头;或 Bearer 携带可用普通 API 令牌(sk-);或有效 access token。 func TFOpenSyncExportChannels(c *gin.Context) { if !authorizeTFOpenSyncExport(c) { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "无权导出:请使用同步密钥(X-TokenFactory-Open-Sync-Secret)或 Bearer 携带可用令牌(sk- 或 access token)", }) return } var channels []*model.Channel q := model.DB.Model(&model.Channel{}). Omit("key"). Where("type <> ?", constant.ChannelTypeTokenFactoryOpen). Order("supplier_application_id asc, channel_no asc, id asc") if err := q.Find(&channels).Error; err != nil { common.SysError("tf_open_sync export: " + err.Error()) c.JSON(http.StatusOK, gin.H{"success": false, "message": "查询渠道失败"}) return } appIDs := make([]int, 0) seen := make(map[int]struct{}) for _, ch := range channels { if ch != nil && ch.SupplierApplicationID > 0 { if _, ok := seen[ch.SupplierApplicationID]; !ok { seen[ch.SupplierApplicationID] = struct{}{} appIDs = append(appIDs, ch.SupplierApplicationID) } } } aliasByAppID := make(map[int]string, len(appIDs)) logoByAppID := make(map[int]string, len(appIDs)) supplierTypeByAppID := make(map[int]string, len(appIDs)) if len(appIDs) > 0 { type appRow struct { ID int `gorm:"column:id"` Alias string `gorm:"column:supplier_alias"` LogoURL string `gorm:"column:company_logo_url"` SupplierType string `gorm:"column:supplier_type"` } var apps []appRow if err := model.DB.Table("supplier_applications"). Select("id, supplier_alias, company_logo_url, supplier_type"). Where("id IN ?", appIDs). Scan(&apps).Error; err == nil { for _, a := range apps { aliasByAppID[a.ID] = strings.TrimSpace(a.Alias) logoByAppID[a.ID] = strings.TrimSpace(a.LogoURL) supplierTypeByAppID[a.ID] = strings.TrimSpace(a.SupplierType) } } } priceAll := ratio_setting.GetChannelModelPriceCopy() ratioAll := ratio_setting.GetChannelModelRatioCopy() out := make([]tfOpenSyncExportRow, 0, len(channels)) for _, ch := range channels { if ch == nil { continue } idStr := strconv.Itoa(ch.Id) mp := priceAll[idStr] mr := ratioAll[idStr] if len(mp) == 0 { mp = nil } if len(mr) == 0 { mr = nil } // 仅导出该渠道 models 列表中出现的模型,控制体积 modelSet := make(map[string]struct{}) for _, m := range ch.GetModels() { mk := ratio_setting.FormatMatchingModelName(m) if mk != "" { modelSet[mk] = struct{}{} } } if len(modelSet) > 0 { filteredP := make(map[string]float64) filteredR := make(map[string]float64) for mk := range modelSet { if mp != nil { if v, ok := mp[mk]; ok { filteredP[mk] = v } } if mr != nil { if v, ok := mr[mk]; ok { filteredR[mk] = v } } } if len(filteredP) == 0 { filteredP = nil } if len(filteredR) == 0 { filteredR = nil } mp, mr = filteredP, filteredR } out = append(out, tfOpenSyncExportRow{ ID: ch.Id, Name: ch.Name, Models: ch.Models, Group: ch.Group, Status: ch.Status, Type: ch.Type, ChannelNo: strings.TrimSpace(ch.ChannelNo), SupplierApplicationID: ch.SupplierApplicationID, SupplierAlias: aliasByAppID[ch.SupplierApplicationID], SupplierType: coalesceStr(supplierTypeByAppID[ch.SupplierApplicationID], strings.TrimSpace(ch.SupplierType)), CompanyLogoURL: coalesceStr(logoByAppID[ch.SupplierApplicationID], strings.TrimSpace(ch.CompanyLogoURL)), PriceDiscountPercent: ch.PriceDiscountPercent, MarkupDiscountRate: ch.MarkupDiscountRate, ModelMapping: strings.TrimSpace(ch.GetModelMapping()), ModelPrice: mp, ModelRatio: mr, }) } common.ApiSuccess(c, gin.H{"channels": out}) }