fix: websearch features_config cleanup and pricing rules validation

- Fix web_search_emulation toggle: explicitly write false for disabled
  platforms instead of leaving stale true from cloned features_config
- Extract validatePricingEntries from validateChannelConfig for reuse
- Validate account_stats_pricing_rules[].pricing in both Create and
  Update paths (negative prices, bad intervals, missing per_request price)
This commit is contained in:
erio
2026-04-14 00:42:40 +08:00
parent a9880ee7b9
commit 9c09bd19b4
2 changed files with 25 additions and 5 deletions

View File

@@ -566,15 +566,21 @@ func ReplaceModelInBody(body []byte, newModel string) []byte {
// validateChannelConfig 校验渠道的定价和映射配置(冲突检测 + 区间校验 + 计费模式校验)。
// Create 和 Update 共用此函数,避免重复。
func validateChannelConfig(pricing []ChannelModelPricing, mapping map[string]map[string]string) error {
if err := validatePricingEntries(pricing); err != nil {
return err
}
return validateNoConflictingMappings(mapping)
}
// validatePricingEntries 校验定价条目(冲突检测 + 区间校验 + 计费模式校验),
// 同时用于主渠道定价和 account_stats_pricing_rules 的内部定价。
func validatePricingEntries(pricing []ChannelModelPricing) error {
if err := validateNoConflictingModels(pricing); err != nil {
return err
}
if err := validatePricingIntervals(pricing); err != nil {
return err
}
if err := validateNoConflictingMappings(mapping); err != nil {
return err
}
return validatePricingBillingMode(pricing)
}
@@ -684,6 +690,11 @@ func (s *ChannelService) Create(ctx context.Context, input *CreateChannelInput)
if err := validateChannelConfig(channel.ModelPricing, channel.ModelMapping); err != nil {
return nil, err
}
for i, rule := range channel.AccountStatsPricingRules {
if err := validatePricingEntries(rule.Pricing); err != nil {
return nil, fmt.Errorf("account stats pricing rule #%d: %w", i+1, err)
}
}
if err := s.repo.Create(ctx, channel); err != nil {
return nil, fmt.Errorf("create channel: %w", err)
@@ -712,6 +723,11 @@ func (s *ChannelService) Update(ctx context.Context, id int64, input *UpdateChan
if err := validateChannelConfig(channel.ModelPricing, channel.ModelMapping); err != nil {
return nil, err
}
for i, rule := range channel.AccountStatsPricingRules {
if err := validatePricingEntries(rule.Pricing); err != nil {
return nil, fmt.Errorf("account stats pricing rule #%d: %w", i+1, err)
}
}
oldGroupIDs := s.getOldGroupIDs(ctx, id)

View File

@@ -1032,15 +1032,19 @@ function formToAPI(): { group_ids: number[], model_pricing: ChannelModelPricing[
}
// Collect web_search_emulation (only anthropic platform supports it)
// Always write the key so that disabling in the UI correctly sets platform to false,
// rather than leaving a stale true value from the cloned features_config.
const wsEmulation: Record<string, boolean> = {}
for (const section of form.platforms) {
if (!section.enabled) continue
if (section.web_search_emulation && section.platform === 'anthropic') {
wsEmulation[section.platform] = true
if (section.platform === 'anthropic') {
wsEmulation[section.platform] = !!section.web_search_emulation
}
}
if (Object.keys(wsEmulation).length > 0) {
featuresConfig.web_search_emulation = wsEmulation
} else {
delete featuresConfig.web_search_emulation
}
return { group_ids, model_pricing, model_mapping, features_config: featuresConfig }