|
- 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"`
- }
-
- // 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 {
- // 更新
- existing.QuotaType = req.QuotaType
- existing.ModelRatio = req.ModelRatio
- existing.CompletionRatio = req.CompletionRatio
- existing.ModelPrice = req.ModelPrice
- existing.TagIds = req.TagIds
- existing.CacheRatio = req.CacheRatio
- existing.CacheCreationRatio = req.CacheCreationRatio
- existing.ImageRatio = req.ImageRatio
- existing.AudioRatio = req.AudioRatio
- existing.AudioCompletionRatio = req.AudioCompletionRatio
- 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,
- QuotaType: req.QuotaType,
- ModelRatio: req.ModelRatio,
- CompletionRatio: req.CompletionRatio,
- ModelPrice: req.ModelPrice,
- TagIds: req.TagIds,
- CacheRatio: req.CacheRatio,
- CacheCreationRatio: req.CacheCreationRatio,
- ImageRatio: req.ImageRatio,
- AudioRatio: req.AudioRatio,
- AudioCompletionRatio: req.AudioCompletionRatio,
- }
- 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 {
- 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,
- CacheRatio: item.CacheRatio,
- CacheCreationRatio: item.CacheCreationRatio,
- ImageRatio: item.ImageRatio,
- AudioRatio: item.AudioRatio,
- AudioCompletionRatio: item.AudioCompletionRatio,
- })
- }
-
- // 使用事务逐个处理(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
- existing.CacheRatio = cp.CacheRatio
- existing.CacheCreationRatio = cp.CacheCreationRatio
- existing.ImageRatio = cp.ImageRatio
- existing.AudioRatio = cp.AudioRatio
- existing.AudioCompletionRatio = 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 := ratio_setting.GetAudioRatio(ability.Model)
- globalAudioCompletionRatio := ratio_setting.GetAudioCompletionRatio(ability.Model)
-
- // 确定定价类型
- 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
- existing.CacheRatio = globalCacheRatio
- existing.CacheCreationRatio = globalCacheCreationRatio
- existing.ImageRatio = globalImageRatio
- existing.AudioRatio = globalAudioRatio
- existing.AudioCompletionRatio = globalAudioCompletionRatio
- if err := existing.Update(); err == nil {
- imported++
- }
- } else {
- cp := &model.ChannelPricing{
- ModelName: ability.Model,
- ChannelId: channelId,
- QuotaType: quotaType,
- ModelRatio: ratio,
- CompletionRatio: completionRatio,
- ModelPrice: price,
- CacheRatio: globalCacheRatio,
- CacheCreationRatio: globalCacheCreationRatio,
- ImageRatio: globalImageRatio,
- AudioRatio: globalAudioRatio,
- AudioCompletionRatio: 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,
- })
- }
|