refactor(*): 稳定期治理 - 收敛核心架构、错误语义并修复跨项目数据竞态

This commit is contained in:
ddCat committed 2026-09-09 22:02:10 +08:00
1 parent bacd502f5c
commit b5f3413ded
298 files changed
+12681 -26428

No files matched your search

+125 -20
View File
@@ -12,6 +12,9 @@ import (
// CreateTask 收敛任务进入系统前的 admission 流程:输入标准化、逻辑模型路由、
// 能力/额度校验和持久化。执行阶段由 worker 与 provider 相关模块负责。
// CreateTask 校验并创建一条生成任务。
// 这是常规模型生成任务的写入口:客户端只提交创作意图,模型、渠道、协议和计价信息必须由服务端目录重新解析,
// 以保证“可展示的模型”与“实际执行及扣费的模型”来自同一份有效配置。
func (s *Service) CreateTask(userID string, req CreateTaskRequest) (*model.Task, error) {
if s.IsDraining() {
return nil, &AppError{Status: 503, Code: 503, Message: "服务正在维护,暂不接受新的生成任务", Retryable: true}
@@ -152,9 +155,11 @@ func (s *Service) resolveTaskModelSelection(input map[string]any, logicalModelID
// 自定义渠道没有系统 channelId;它会在后续由自定义渠道功能开关、
// 能力校验和 provider 配置校验共同处理,不能误报为“缺少系统渠道”。
if !customChannelTask {
if err := s.validateSystemChannelModelSelection(input); err != nil {
resolvedInput, err := s.resolveSystemChannelModelSelection(input, taskType, operation)
if err != nil {
return nil, input, err
}
return nil, resolvedInput, nil
}
return nil, input, nil
}
@@ -284,47 +289,142 @@ func (s *Service) requireCustomChannelsForTaskInput(input map[string]any) error
return s.RequireFeature(FeatureCustomChannels)
}
// validateSystemChannelModelSelection 校验系统渠道模型选择的有效性
func (s *Service) validateSystemChannelModelSelection(input map[string]any) error {
// resolveSystemChannelModelSelection 是系统渠道任务的 admission 边界。
// 客户端只负责表达创作参数;模型协议、能力合同、价格档和上游模型标识必须从服务端记录重建,
// 避免出现“按一个规格校验/计费,却按另一个规格执行”的跨阶段漂移。
func (s *Service) resolveSystemChannelModelSelection(input map[string]any, taskType string, operation string) (map[string]any, error) {
config, ok := input["config"].(map[string]any)
if !ok {
return InvalidModelSelection("缺少模型配置")
return input, InvalidModelSelection("缺少模型配置")
}
channelID, _ := config["channelId"].(string)
modelKey, _ := config["model"].(string)
channelID = strings.TrimSpace(channelID)
modelKey = strings.TrimSpace(modelKey)
channelID := strings.TrimSpace(stringValue(config["channelId"]))
modelKey := strings.TrimPrefix(strings.TrimSpace(stringValue(config["model"])), "models/")
if channelID == "" || modelKey == "" {
return InvalidModelSelection("必须指定系统渠道和模型")
return input, InvalidModelSelection("必须指定系统渠道和模型")
}
// 验证渠道存在且启用
channel, err := s.repo.SystemChannel(channelID)
if err != nil {
return InvalidModelSelection("指定的渠道不存在")
return input, InvalidModelSelection("指定的渠道不存在")
}
if !channel.Enabled || channel.Scope != model.ChannelScopeSystem {
return InvalidModelSelection("指定的渠道不可用")
return input, InvalidModelSelection("指定的渠道不可用")
}
// 验证渠道模型存在且启用
channelModel, err := s.repo.ChannelModelByKey(channelID, modelKey)
if err != nil {
return InvalidModelSelection("指定的模型不存在")
return input, InvalidModelSelection("指定的模型不存在")
}
if !channelModel.Enabled {
return InvalidModelSelection("指定的模型已停用")
return input, InvalidModelSelection("指定的模型已停用")
}
if channelModel.Protocol == "" {
return input, InvalidModelSelection("指定的模型未配置请求协议")
}
// 验证价格配置
if !HasValidPrice(channelModel) {
return ModelPriceNotConfigured("指定的模型未配置有效价格")
nextConfig := make(map[string]any, len(config)+6)
for key, value := range config {
switch key {
case "channelId", "channelModelKey", "priceTierId", "providerModelKey", "apiFormat", "interfaceType", "baseUrl", "allowLocalChannel", "apiKey", "secretKey", "headers", "model", "capabilityConfig":
continue
default:
nextConfig[key] = value
}
}
// capabilityOptions 是路由、校验和计价共同使用的请求规格;存在时必须覆盖 config 中的同名字段,
// 不能让两份客户端数据分别驱动计费与真实上游请求。
if options, ok := input["capabilityOptions"].(map[string]any); ok {
for key, value := range options {
canonical := canonicalCapabilityOptionName(key)
if isCapabilityOptionFor(channelModel.Capability, canonical) {
nextConfig[canonical] = value
}
}
}
return nil
capabilityConfig, err := normalizedChannelModelCapability(channelModel)
if err != nil {
return input, InvalidModelSelection("指定的模型能力配置无效,请联系管理员")
}
applyChannelCapabilityDefaults(nextConfig, channelModel.Capability, capabilityConfig)
input["config"] = nextConfig
input["capabilityOptions"] = capabilityOptionsFromConfig(channelModel.Capability, nextConfig)
intent := ModelRequestIntentFromTaskInput(input, taskType, operation)
if normalizeCapability(intent.Capability) != normalizeCapability(channelModel.Capability) {
return input, ModelCapabilityNotSupported("所选模型与任务能力不匹配")
}
if normalizeCapability(channelModel.Capability) != "audio" {
spec, specErr := CapabilitySpecFromModelCapabilityConfig(capabilityConfig, channelModel.Capability)
if specErr != nil {
return input, InvalidModelSelection("指定的模型能力配置无效,请联系管理员")
}
if match := MatchCapability(spec, intent); !match.Matched {
return input, ModelCapabilityNotSupported("所选模型不支持当前请求:" + strings.Join(match.Reasons, ";"))
}
}
priceTier := channelModelPriceTierForIntent(*channelModel, intent)
if priceTier == nil || !ValidatePriceTierPrice(priceTier, channelModel.Capability, channelModel.Protocol) {
return input, ModelPriceNotConfigured("指定的模型未配置当前规格的有效价格")
}
nextConfig["channelId"] = channel.ID
nextConfig["model"] = channelModel.ModelKey
nextConfig["channelModelKey"] = channelModel.ModelKey
nextConfig["priceTierId"] = priceTier.ID
nextConfig["providerModelKey"] = firstNonEmpty(priceTier.ProviderModelKey, channelModel.ProviderModelKey, channelModel.ModelKey)
nextConfig["interfaceType"] = string(channelModel.Protocol)
nextConfig["apiFormat"] = channelAPIFormatForProtocol(channel.APIFormat, channelModel.Protocol)
return input, nil
}
// applyChannelCapabilityDefaults 只采用管理员保存的能力默认值,且仅填补客户端未表达的参数。
// 这些值随后会写回 capabilityOptions,使能力校验、SKU 选择和 provider 执行看到同一份规格。
func applyChannelCapabilityDefaults(config map[string]any, capability string, profile *ModelCapabilityConfig) {
setDefault := func(key string, value any) {
if existing, exists := config[key]; !exists || existing == nil || strings.TrimSpace(fmt.Sprint(existing)) == "" {
config[key] = value
}
}
switch normalizeCapability(capability) {
case "image":
if profile == nil || profile.Image == nil {
return
}
if profile.Image.Size.Parameter != "none" {
setDefault("size", profile.Image.Size.Default)
}
if profile.Image.Quality.Supported {
setDefault("quality", profile.Image.Quality.Default)
}
setDefault("transparentBackground", profile.Image.TransparentBackground.Default)
setDefault("count", 1)
case "video":
if profile == nil || profile.Video == nil {
return
}
if videoDurationSupported(profile.Video) {
setDefault("videoSeconds", profile.Video.Duration.Default)
}
setDefault("size", profile.Video.DefaultRatio)
setDefault("vquality", profile.Video.DefaultResolution)
setDefault("videoGenerateAudio", profile.Video.GenerateAudio.Default)
setDefault("videoWatermark", profile.Video.Watermark.Default)
}
}
func capabilityOptionsFromConfig(capability string, config map[string]any) map[string]any {
options := map[string]any{}
for key, value := range config {
canonical := canonicalCapabilityOptionName(key)
if isCapabilityOptionFor(capability, canonical) && value != nil && strings.TrimSpace(fmt.Sprint(value)) != "" {
options[canonical] = value
}
}
return options
}
func taskInputUsesCustomChannel(input map[string]any) bool {
@@ -358,6 +458,11 @@ func taskInputUsesWorkflowProvider(input map[string]any) bool {
if !ok {
return false
}
// 系统渠道的 interfaceType 是客户端缓存,不是授权事实;必须先走系统模型 admission,
// 不能通过伪造工作流协议绕开渠道模型、能力和价格校验。
if strings.TrimSpace(stringValue(config["channelId"])) != "" {
return false
}
return isWorkflowProviderInterface(strings.TrimSpace(fmt.Sprint(config["interfaceType"])))
}