package controller import ( "fmt" "strconv" "strings" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/model" "github.com/QuantumNous/new-api/setting/ratio_setting" "github.com/gin-gonic/gin" ) // GetAllChannelPricing 获取所有渠道定价(分页) func GetAllChannelPricing(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("p", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10")) offset := (page - 1) * pageSize list, total, err := model.GetAllChannelPricing(offset, pageSize) if err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, gin.H{ "page": page, "page_size": pageSize, "total": total, "items": list, }) } // GetChannelPricingByModel 获取指定模型的所有渠道定价 func GetChannelPricingByModel(c *gin.Context) { modelName := c.Param("name") if modelName == "" { common.ApiErrorMsg(c, "model name is required") return } list, err := model.GetChannelPricingByModel(modelName) if err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, list) } // CreateChannelPricingRequest 创建渠道定价请求 type CreateChannelPricingRequest struct { Id int `json:"id"` ModelName string `json:"model_name" binding:"required"` ChannelId int `json:"channel_id" binding:"required"` QuotaType int `json:"quota_type"` ModelRatio float64 `json:"model_ratio"` CompletionRatio float64 `json:"completion_ratio"` ModelPrice float64 `json:"model_price"` TagIds string `json:"tag_ids"` CacheRatio float64 `json:"cache_ratio"` CacheCreationRatio float64 `json:"cache_creation_ratio"` ImageRatio float64 `json:"image_ratio"` AudioRatio float64 `json:"audio_ratio"` AudioCompletionRatio float64 `json:"audio_completion_ratio"` } // applyRequest 将请求字段应用到 ChannelPricing func applyRequestFields(cp *model.ChannelPricing, req *CreateChannelPricingRequest) { cp.ApplyFields(req.QuotaType, req.ModelRatio, req.CompletionRatio, req.ModelPrice, req.TagIds, req.CacheRatio, req.CacheCreationRatio, req.ImageRatio, req.AudioRatio, req.AudioCompletionRatio) } // CreateChannelPricing 创建或更新渠道定价 func CreateChannelPricing(c *gin.Context) { var req CreateChannelPricingRequest if err := c.ShouldBindJSON(&req); err != nil { common.ApiError(c, err) return } if req.CacheRatio < 0 || req.CacheCreationRatio < 0 || req.ImageRatio < 0 || req.AudioRatio < 0 || req.AudioCompletionRatio < 0 { common.ApiErrorMsg(c, "ratio values must be >= 0") return } // 检查是否已存在 existing, _ := model.GetChannelPricing(req.ModelName, req.ChannelId) if existing != nil { applyRequestFields(existing, &req) if err := existing.Update(); err != nil { common.ApiError(c, err) return } common.SysLog(fmt.Sprintf("[ChannelPricing] updated: id=%d model=%s channel=%d", existing.Id, existing.ModelName, existing.ChannelId)) common.ApiSuccess(c, existing) return } // 创建 cp := &model.ChannelPricing{ ModelName: req.ModelName, ChannelId: req.ChannelId, } applyRequestFields(cp, &req) if err := cp.Insert(); err != nil { common.ApiError(c, err) return } common.SysLog(fmt.Sprintf("[ChannelPricing] created: model=%s channel=%d quotaType=%d modelRatio=%.4f completionRatio=%.4f modelPrice=%.4f cacheRatio=%.4f cacheCreationRatio=%.4f imageRatio=%.4f audioRatio=%.4f audioCompletionRatio=%.4f", req.ModelName, req.ChannelId, req.QuotaType, req.ModelRatio, req.CompletionRatio, req.ModelPrice, req.CacheRatio, req.CacheCreationRatio, req.ImageRatio, req.AudioRatio, req.AudioCompletionRatio)) common.ApiSuccess(c, cp) } // BatchCreateChannelPricingRequest 批量创建请求 type BatchCreateChannelPricingRequest struct { Items []*CreateChannelPricingRequest `json:"items" binding:"required"` } // BatchCreateChannelPricing 批量创建或更新渠道定价 func BatchCreateChannelPricing(c *gin.Context) { var req BatchCreateChannelPricingRequest if err := c.ShouldBindJSON(&req); err != nil { common.ApiError(c, err) return } pricings := make([]*model.ChannelPricing, 0, len(req.Items)) for _, item := range req.Items { cp := &model.ChannelPricing{ ModelName: item.ModelName, ChannelId: item.ChannelId, } applyRequestFields(cp, item) pricings = append(pricings, cp) } // 使用事务逐个处理(GORM 的批量 upsert 在不同数据库表现不一致) for _, cp := range pricings { existing, _ := model.GetChannelPricing(cp.ModelName, cp.ChannelId) if existing != nil { existing.ApplyFields(cp.QuotaType, cp.ModelRatio, cp.CompletionRatio, cp.ModelPrice, cp.TagIds, cp.CacheRatio, cp.CacheCreationRatio, cp.ImageRatio, cp.AudioRatio, cp.AudioCompletionRatio) if err := existing.Update(); err != nil { common.ApiError(c, err) return } } else { if err := cp.Insert(); err != nil { common.ApiError(c, err) return } } } common.ApiSuccess(c, gin.H{"affected": len(pricings)}) } // DeleteChannelPricing 删除渠道定价 func DeleteChannelPricing(c *gin.Context) { idStr := c.Param("id") id, err := strconv.Atoi(idStr) if err != nil { common.ApiError(c, err) return } cp := &model.ChannelPricing{Id: id} if err := cp.Delete(); err != nil { common.ApiError(c, err) return } common.SysLog(fmt.Sprintf("[ChannelPricing] deleted: id=%d", id)) common.ApiSuccess(c, nil) } // CopyGlobalPricingRequest 复制全局定价请求 type CopyGlobalPricingRequest struct { Overwrite bool `json:"overwrite"` // 是否覆盖已存在的渠道定价 } // CopyGlobalPricing 复制全局定价到指定渠道 // 从 ratio_setting 读取全局定价信息,复制到 channel_pricing 表 func CopyGlobalPricing(c *gin.Context) { channelIdStr := c.Param("channel_id") channelId, err := strconv.Atoi(channelIdStr) if err != nil { common.ApiError(c, err) return } var req CopyGlobalPricingRequest c.ShouldBindJSON(&req) // 获取该渠道支持的所有模型(使用 GetAbilitiesByChannelId) abilities, err := model.GetAbilitiesByChannelId(channelId) if err != nil { common.ApiError(c, err) return } imported := 0 for _, ability := range abilities { // 检查是否已存在 existing, _ := model.GetChannelPricing(ability.Model, channelId) if existing != nil && !req.Overwrite { continue } // 获取全局扩展比率 globalCacheRatio, hasCacheRatio := ratio_setting.GetCacheRatio(ability.Model) if !hasCacheRatio { globalCacheRatio = 0 } globalCacheCreationRatio, hasCacheCreationRatio := ratio_setting.GetCreateCacheRatio(ability.Model) if !hasCacheCreationRatio { globalCacheCreationRatio = 0 } globalImageRatio, hasImageRatio := ratio_setting.GetImageRatio(ability.Model) if !hasImageRatio { globalImageRatio = 0 } globalAudioRatio, hasAudioRatio := ratio_setting.GetAudioRatioV2(ability.Model) if !hasAudioRatio { globalAudioRatio = 0 } globalAudioCompletionRatio, hasAudioCompRatio := ratio_setting.GetAudioCompletionRatioV2(ability.Model) if !hasAudioCompRatio { globalAudioCompletionRatio = 0 } // 确定定价类型 var quotaType int var ratio, completionRatio, price float64 // 优先检查是否有按次计费的价格 modelPrice, hasPrice := ratio_setting.GetModelPrice(ability.Model, false) if hasPrice { quotaType = model.QuotaTypeByCall price = modelPrice } else { // 使用按量计费 quotaType = model.QuotaTypeByTokens modelRatio, hasRatio, _ := ratio_setting.GetModelRatio(ability.Model) if hasRatio { ratio = modelRatio } completionRatio = ratio_setting.GetCompletionRatio(ability.Model) } if existing != nil { existing.ApplyFields(quotaType, ratio, completionRatio, price, "", globalCacheRatio, globalCacheCreationRatio, globalImageRatio, globalAudioRatio, globalAudioCompletionRatio) if err := existing.Update(); err == nil { imported++ } } else { cp := &model.ChannelPricing{ ModelName: ability.Model, ChannelId: channelId, } cp.ApplyFields(quotaType, ratio, completionRatio, price, "", globalCacheRatio, globalCacheCreationRatio, globalImageRatio, globalAudioRatio, globalAudioCompletionRatio) if err := cp.Insert(); err == nil { imported++ } } } common.SysLog(fmt.Sprintf("[ChannelPricing] copyGlobalPricing: channel=%d imported=%d/%d", channelId, imported, len(abilities))) common.ApiSuccess(c, gin.H{ "total": len(abilities), "imported": imported, }) } // ChannelPricingWithTags 带标签详情的渠道定价响应 type ChannelPricingWithTags struct { *model.ChannelPricing Tags []*model.PricingTag `json:"tags"` } // GetChannelPricingByModelWithChannelInfo 获取指定模型的渠道定价(带渠道信息,所有用户可访问) func GetChannelPricingByModelWithChannelInfo(c *gin.Context) { // 使用通配符路由时,参数包含前导斜杠,需要去除 modelName := c.Param("name") if modelName == "" { common.ApiErrorMsg(c, "model name is required") return } // 去除前导斜杠(路由是 /channel-pricing/model/*name,name 会是 "/deepseek-ai/xxx") modelName = strings.TrimPrefix(modelName, "/") list, err := model.GetChannelPricingByModelWithChannelInfo(modelName) if err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, list) } // GetChannelPricingWithTags 获取渠道定价(带标签详情) func GetChannelPricingWithTags(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("p", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10")) offset := (page - 1) * pageSize list, total, err := model.GetAllChannelPricing(offset, pageSize) if err != nil { common.ApiError(c, err) return } // 获取所有标签 tags, _ := model.GetAllPricingTags() tagMap := make(map[int]*model.PricingTag) for _, tag := range tags { tagMap[tag.Id] = tag } // 为每个定价填充标签详情 result := make([]*ChannelPricingWithTags, 0, len(list)) for _, cp := range list { item := &ChannelPricingWithTags{ ChannelPricing: cp, Tags: model.ParseTagIds(cp.TagIds, tagMap), } result = append(result, item) } common.ApiSuccess(c, gin.H{ "page": page, "page_size": pageSize, "total": total, "items": result, }) }