131 lines
5.1 KiB
Go
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,
|
||
})
|
||
}
|