Files
ttstd-infinite-canvas/backend/internal/app/channel_model_pricing.go
T

131 lines
5.1 KiB
Go

package app
import (
"errors"
"fmt"
"sort"
"strings"
"infinite-canvas/backend/internal/model"
"infinite-canvas/backend/internal/repository"
)
type ChannelModelRepriceRequest struct {
ModelID string `json:"modelId"`
PriceVersion int64 `json:"priceVersion"`
PriceTiers []ChannelModelTierRepriceRequest `json:"priceTiers"`
}
type ChannelModelTierRepriceRequest struct {
ID string `json:"id"`
PriceVersion int64 `json:"priceVersion"`
Prices map[string]*int64 `json:"prices"`
}
// RepriceAdminChannelModels only changes sale prices, never model availability or cost.
func (s *Service) RepriceAdminChannelModels(actor *model.User, channelID string, requests []ChannelModelRepriceRequest) (int, error) {
if err := s.RequireAdmin(actor); err != nil {
return 0, err
}
if _, err := s.adminSystemChannel(channelID); err != nil {
return 0, err
}
if len(requests) == 0 || len(requests) > 100 {
return 0, BadAuthRequest("单次请选择 1 到 100 个渠道模型")
}
selected := make(map[string]ChannelModelRepriceRequest, len(requests))
for _, request := range requests {
id := strings.TrimSpace(request.ModelID)
if _, duplicate := selected[id]; id == "" || duplicate || request.PriceVersion < 1 {
return 0, BadAuthRequest("渠道模型 ID 或价格版本无效,且模型不能重复")
}
selected[id] = request
}
items, err := s.repo.ChannelModels(channelID, true)
if err != nil {
return 0, err
}
updates := make([]model.ChannelModel, 0, len(selected))
for _, item := range items {
request, exists := selected[item.ID]
if !exists {
continue
}
if request.PriceVersion != item.PriceVersion {
return 0, BadAuthRequest("模型价格已变化,请刷新后重新调价;本次未修改任何价格")
}
tiers := make(map[string]ChannelModelTierRepriceRequest, len(request.PriceTiers))
for _, tier := range request.PriceTiers {
if _, duplicate := tiers[tier.ID]; tier.ID == "" || duplicate {
return 0, BadAuthRequest("规格 ID 不能为空或重复")
}
tiers[tier.ID] = tier
}
active := 0
for index := range item.PriceTiers {
tier := &item.PriceTiers[index]
if !tier.Enabled {
continue
}
active++
input, exists := tiers[tier.ID]
if !exists || input.PriceVersion != tier.PriceVersion {
return 0, BadAuthRequest("规格价格已变化或提交不完整,请刷新后重新调价")
}
if err := applyChannelModelTierPrices(tier, item.Capability, item.Protocol, input.Prices); err != nil {
return 0, BadAuthRequest(fmt.Sprintf("模型 %s(规格 %s):%s;本次未修改任何价格", item.ModelKey, tier.SelectorKey, err.Error()))
}
}
if active == 0 || active != len(tiers) {
return 0, BadAuthRequest("模型 " + item.ModelKey + " 的规格提交不完整或包含无效规格;本次未修改任何价格")
}
s.applyChannelModelPriceTierSummary(&item, item.PriceTiers)
updates = append(updates, item)
}
if len(updates) != len(selected) {
return 0, BadAuthRequest("所选模型已删除或不属于当前渠道,请刷新后重试")
}
// Consistent lock order for overlapping batch requests.
sort.Slice(updates, func(i, j int) bool { return updates[i].ID < updates[j].ID })
if err := s.repo.UpdateChannelModelSalePrices(channelID, updates); err != nil {
if errors.Is(err, repository.ErrChannelModelPriceConflict) {
return 0, BadAuthRequest("模型价格已被其他操作修改,请刷新后重试;本次未修改任何价格")
}
return 0, err
}
s.invalidateRouteCatalog()
return len(updates), nil
}
func applyChannelModelTierPrices(tier *model.ChannelModelPriceTier, capability string, protocol model.ChannelInterfaceType, prices map[string]*int64) error {
if tier.ID == "" || !tier.PriceConfigured || !tier.CostPricing.Configured {
return BadAuthRequest("请先保存完整的规格成本价和销售价配置")
}
if err := validateCreditCostPricing(capability, tier.BillingMode, tier.CostPricing); err != nil {
return err
}
fields := map[string]*int64{"unitPriceMicrocredits": &tier.UnitPriceMicrocredits}
if tier.BillingMode == "token" {
fields = map[string]*int64{"outputTokenPriceMicrocredits": &tier.OutputTokenPriceMicrocredits}
if capability != "video" {
fields["inputTokenPriceMicrocredits"] = &tier.InputTokenPriceMicrocredits
fields["cachedTokenPriceMicrocredits"] = &tier.CachedTokenPriceMicrocredits
}
}
if len(prices) != len(fields) {
return BadAuthRequest("必须提交当前计费方式的全部销售价,且不能包含其他字段")
}
for key, target := range fields {
value, exists := prices[key]
if !exists || value == nil || *value < 0 || *value > 9_007_199_254_740_991 {
return BadAuthRequest("销售价缺失或超出有效范围")
}
*target = *value
}
return validateChannelModelTierPricing(capability, protocol, tier.BillingMode, ChannelModelPriceTierRequest{
CostPricing: tier.CostPricing, UnitPriceMicrocredits: tier.UnitPriceMicrocredits,
InputTokenPriceMicrocredits: tier.InputTokenPriceMicrocredits, OutputTokenPriceMicrocredits: tier.OutputTokenPriceMicrocredits,
CachedTokenPriceMicrocredits: tier.CachedTokenPriceMicrocredits,
})
}