|
- 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,
- })
- }
|