Browse Source

feat: add ChannelPricing CRUD controller

- GetAllChannelPricing: get all channel pricings with pagination
- GetChannelPricingByModel: get pricings for a specific model
- CreateChannelPricing: create or update channel pricing
- BatchCreateChannelPricing: batch create/update pricings
- DeleteChannelPricing: delete channel pricing
- CopyGlobalPricing: copy global pricing from ratio_setting to channel
- GetChannelPricingWithTags: get pricings with tag details

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat/alipay-payment
fengsilin 1 month ago
parent
commit
447ccd09b2
1 changed files with 305 additions and 0 deletions
  1. +305
    -0
      controller/channel_pricing.go

+ 305
- 0
controller/channel_pricing.go View File

@@ -0,0 +1,305 @@
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"`
}

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

Loading…
Cancel
Save