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"` Required bool `json:"required"` } 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] = false } default: 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 } } return nil } 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 "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 }