package controller import ( "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"` } // CreateChannelPricing 创建或更新渠道定价 func CreateChannelPricing(c *gin.Context) { var req CreateChannelPricingRequest if err := c.ShouldBindJSON(&req); err != nil { common.ApiError(c, err) return } // 检查是否已存在 existing, _ := model.GetChannelPricing(req.ModelName, req.ChannelId) if existing != nil { // 更新 existing.QuotaType = req.QuotaType existing.ModelRatio = req.ModelRatio existing.CompletionRatio = req.CompletionRatio existing.ModelPrice = req.ModelPrice existing.TagIds = req.TagIds if err := existing.Update(); err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, existing) return } // 创建 cp := &model.ChannelPricing{ ModelName: req.ModelName, ChannelId: req.ChannelId, QuotaType: req.QuotaType, ModelRatio: req.ModelRatio, CompletionRatio: req.CompletionRatio, ModelPrice: req.ModelPrice, TagIds: req.TagIds, } if err := cp.Insert(); err != nil { common.ApiError(c, err) return } 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 { pricings = append(pricings, &model.ChannelPricing{ ModelName: item.ModelName, ChannelId: item.ChannelId, QuotaType: item.QuotaType, ModelRatio: item.ModelRatio, CompletionRatio: item.CompletionRatio, ModelPrice: item.ModelPrice, TagIds: item.TagIds, }) } // 使用事务逐个处理(GORM 的批量 upsert 在不同数据库表现不一致) for _, cp := range pricings { existing, _ := model.GetChannelPricing(cp.ModelName, cp.ChannelId) if existing != nil { existing.QuotaType = cp.QuotaType existing.ModelRatio = cp.ModelRatio existing.CompletionRatio = cp.CompletionRatio existing.ModelPrice = cp.ModelPrice existing.TagIds = cp.TagIds 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.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 } // 确定定价类型 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.QuotaType = quotaType existing.ModelRatio = ratio existing.CompletionRatio = completionRatio existing.ModelPrice = price if err := existing.Update(); err == nil { imported++ } } else { cp := &model.ChannelPricing{ ModelName: ability.Model, ChannelId: channelId, QuotaType: quotaType, ModelRatio: ratio, CompletionRatio: completionRatio, ModelPrice: price, } if err := cp.Insert(); err == nil { imported++ } } } 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: make([]*model.PricingTag, 0), } if cp.TagIds != "" { for _, idStr := range strings.Split(cp.TagIds, ",") { if id, err := strconv.Atoi(idStr); err == nil { if tag, ok := tagMap[id]; ok { item.Tags = append(item.Tags, tag) } } } } result = append(result, item) } common.ApiSuccess(c, gin.H{ "page": page, "page_size": pageSize, "total": total, "items": result, }) }