package run import ( "encoding/json" "fmt" "strings" "time" "git.iwork-ai.com/xdc/iqudo-top1/internal/model" ) type answerNodeConfig struct { FieldKey string `json:"field_key"` FieldKeys []string `json:"field_keys"` RequiredFieldKeys []string `json:"required_field_keys"` Required bool `json:"required"` Options []string `json:"options"` } // validateInitialAnswers accepts a partial, externally supplied set of values // when an execution starts. Required fields are still enforced by their // collection nodes, so integrations can supply only the data they possess. func validateInitialAnswers(fields []model.ScenarioField, answers map[string]interface{}) error { fieldMap := make(map[string]model.ScenarioField, len(fields)) for _, field := range fields { fieldMap[field.FieldKey] = field if field.Required && field.SourcePath != "" && isEmptyValue(answers[field.FieldKey]) { return fmt.Errorf("缺少必填输入%s", field.FieldName) } } for key, value := range answers { field, exists := fieldMap[key] if !exists { return fmt.Errorf("传入字段 %s 不存在", key) } if isEmptyValue(value) { continue } if err := validateFieldValue(field, value); err != nil { return err } } return nil } func validateNodeAnswers(node model.SOPNode, fields []model.ScenarioField, answers map[string]interface{}) error { var config answerNodeConfig if len(node.Config) > 0 { if err := json.Unmarshal(node.Config, &config); err != nil { return fmt.Errorf("当前节点配置不正确") } } fieldMap := make(map[string]model.ScenarioField, len(fields)) for _, field := range fields { fieldMap[field.FieldKey] = field } expected := map[string]bool{} switch node.Type { case "question", "choice": if config.FieldKey == "" { return fmt.Errorf("当前节点没有配置采集字段") } expected[config.FieldKey] = config.Required case "form": if len(config.FieldKeys) == 0 { return fmt.Errorf("当前表单没有配置采集字段") } for _, key := range config.FieldKeys { expected[key] = containsString(config.RequiredFieldKeys, key) } default: if node.Type == "knowledge" { return validateKnowledgeNodeAnswers(node, fieldMap, answers) } if len(answers) > 0 { return fmt.Errorf("当前节点不接受字段回答") } return nil } for key := range answers { if _, ok := expected[key]; !ok { return fmt.Errorf("字段 %s 不属于当前节点", key) } } for key, nodeRequired := range expected { field, exists := fieldMap[key] if !exists { return fmt.Errorf("字段 %s 不存在", key) } value, provided := answers[key] required := field.Required || nodeRequired if !provided || isEmptyValue(value) { if required { return fmt.Errorf("请填写%s", field.FieldName) } continue } if err := validateFieldValue(field, value); err != nil { return err } if node.Type == "choice" && !stringAllowed(config.Options, fmt.Sprint(value)) { return fmt.Errorf("%s的选项不正确", field.FieldName) } } return nil } func validateKnowledgeNodeAnswers(node model.SOPNode, fields map[string]model.ScenarioField, answers map[string]interface{}) error { var config knowledgeNodeConfig if json.Unmarshal(node.Config, &config) != nil || config.KnowledgeCollection == nil { if len(answers) > 0 { return fmt.Errorf("当前节点不接受字段回答") } return nil } expected := map[string]bool{} for _, key := range config.KnowledgeCollection.ContextFieldKeys { expected[key] = fields[key].Required } for _, step := range config.KnowledgeCollection.Steps { expected[step.FieldKey] = step.Required } for key := range answers { if _, ok := expected[key]; !ok { return fmt.Errorf("字段 %s 不属于当前节点", key) } } for key, required := range expected { value := answers[key] if isEmptyValue(value) { if required { return fmt.Errorf("请填写%s", knowledgeAnswerName(config, fields, key)) } continue } if field, ok := fields[key]; ok && !isKnowledgeStep(config, key) { if err := validateFieldValue(field, value); err != nil { return err } } if isKnowledgeStep(config, key) { if _, ok := stringValues(value); !ok { return fmt.Errorf("%s必须选择有效选项", knowledgeAnswerName(config, fields, key)) } } } return nil } func knowledgeAnswerName(config knowledgeNodeConfig, fields map[string]model.ScenarioField, key string) string { if field, ok := fields[key]; ok { return field.FieldName } for _, step := range config.KnowledgeCollection.Steps { if step.FieldKey == key { return step.Name } } return key } func isKnowledgeStep(config knowledgeNodeConfig, key string) bool { for _, step := range config.KnowledgeCollection.Steps { if step.FieldKey == key { return true } } return false } func stringValues(value interface{}) ([]string, bool) { switch typed := value.(type) { case string: return []string{typed}, typed != "" case []interface{}: result := make([]string, 0, len(typed)) for _, item := range typed { text, ok := item.(string) if !ok || text == "" { return nil, false } result = append(result, text) } return result, true case []string: return typed, true } return nil, false } func stringAllowed(options []string, selected string) bool { for _, option := range options { if option == selected { return true } } return false } func validateFieldValue(field model.ScenarioField, value interface{}) error { switch field.FieldType { case "text", "textarea": text, ok := value.(string) if !ok { return fmt.Errorf("%s必须是文本", field.FieldName) } var rules struct { MinLength int `json:"min_length"` MaxLength int `json:"max_length"` } _ = json.Unmarshal(field.Validation, &rules) length := len([]rune(text)) if rules.MinLength > 0 && length < rules.MinLength { return fmt.Errorf("%s不能少于%d个字符", field.FieldName, rules.MinLength) } if rules.MaxLength > 0 && length > rules.MaxLength { return fmt.Errorf("%s不能超过%d个字符", field.FieldName, rules.MaxLength) } case "number": number, ok := toFloat(value) if !ok { return fmt.Errorf("%s必须是数字", field.FieldName) } var rules struct { Min *float64 `json:"min"` Max *float64 `json:"max"` } _ = json.Unmarshal(field.Validation, &rules) if rules.Min != nil && number < *rules.Min { return fmt.Errorf("%s不能小于%v", field.FieldName, *rules.Min) } if rules.Max != nil && number > *rules.Max { return fmt.Errorf("%s不能大于%v", field.FieldName, *rules.Max) } case "boolean": if _, ok := value.(bool); !ok { return fmt.Errorf("%s必须选择是或否", field.FieldName) } case "select": selected, ok := value.(string) if !ok || !optionAllowed(field.Options, selected) { return fmt.Errorf("%s的选项不正确", field.FieldName) } case "multiselect": values, ok := value.([]interface{}) if !ok { return fmt.Errorf("%s必须是多选值", field.FieldName) } for _, item := range values { selected, ok := item.(string) if !ok || !optionAllowed(field.Options, selected) { return fmt.Errorf("%s包含不正确的选项", field.FieldName) } } case "array": switch values := value.(type) { case []interface{}: _ = values case []string: _ = values default: return fmt.Errorf("%s必须是数组", field.FieldName) } case "date": text, ok := value.(string) if !ok || !validDate(text) { return fmt.Errorf("%s的日期格式不正确", field.FieldName) } default: return fmt.Errorf("%s的字段类型不支持", field.FieldName) } return nil } func optionAllowed(raw []byte, selected string) bool { var options []string if err := json.Unmarshal(raw, &options); err != nil { return false } for _, option := range options { if option == selected { return true } } return false } func validDate(value string) bool { for _, layout := range []string{time.RFC3339, "2006-01-02"} { if _, err := time.Parse(layout, value); err == nil { return true } } return false } func isEmptyValue(value interface{}) bool { if value == nil { return true } if text, ok := value.(string); ok { return strings.TrimSpace(text) == "" } if values, ok := value.([]interface{}); ok { return len(values) == 0 } return false }