package validator import ( "fmt" "regexp" "strings" "unicode/utf8" validate "git.ikuban.com/server/kubanapis/kuban/api/validate" ) // validateString 验证字符串 func (e *Engine) validateString( vctx *ValidationContext, fieldRules *validate.FieldRules, rules *validate.StringRules, value string, fieldPath string, errors *ValidationErrors, ) error { // const 验证 if rules.Const != nil && value != *rules.Const { defaultMsg := fmt.Sprintf("字段 %s 必须等于 '%s'", fieldPath, *rules.Const) errors.Add(NewValidationError( fieldPath, ErrCodeInvalidValue, e.getErrorMessage(fieldRules, vctx, "const", defaultMsg), )) if vctx.ShouldFailFast() { return errors } } // 长度验证(字符数) runeCount := utf8.RuneCountInString(value) if rules.Len != nil && uint64(runeCount) != *rules.Len { defaultMsg := fmt.Sprintf("字段 %s 长度必须为 %d 个字符", fieldPath, *rules.Len) errors.Add(NewValidationError( fieldPath, ErrCodeInvalidLength, e.getErrorMessage(fieldRules, vctx, "len", defaultMsg), )) } if rules.MinLen != nil && uint64(runeCount) < *rules.MinLen { defaultMsg := fmt.Sprintf("字段 %s 长度至少为 %d 个字符", fieldPath, *rules.MinLen) errors.Add(NewValidationError( fieldPath, ErrCodeInvalidLength, e.getErrorMessage(fieldRules, vctx, "min_len", defaultMsg), )) } if rules.MaxLen != nil && uint64(runeCount) > *rules.MaxLen { defaultMsg := fmt.Sprintf("字段 %s 长度最多为 %d 个字符", fieldPath, *rules.MaxLen) errors.Add(NewValidationError( fieldPath, ErrCodeInvalidLength, e.getErrorMessage(fieldRules, vctx, "max_len", defaultMsg), )) } // 字节长度验证 byteLen := len(value) if rules.LenBytes != nil && uint64(byteLen) != *rules.LenBytes { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidLength, fmt.Sprintf("字段 %s 字节长度必须为 %d", fieldPath, *rules.LenBytes), )) } if rules.MinBytes != nil && uint64(byteLen) < *rules.MinBytes { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidLength, fmt.Sprintf("字段 %s 字节长度至少为 %d", fieldPath, *rules.MinBytes), )) } if rules.MaxBytes != nil && uint64(byteLen) > *rules.MaxBytes { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidLength, fmt.Sprintf("字段 %s 字节长度最多为 %d", fieldPath, *rules.MaxBytes), )) } // 模式验证(正则表达式) if rules.Pattern != nil { matched, err := regexp.MatchString(*rules.Pattern, value) if err != nil { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidPattern, fmt.Sprintf("正则表达式错误: %v", err), )) } else if !matched { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidPattern, fmt.Sprintf("字段 %s 格式不正确", fieldPath), )) } } // 前缀/后缀验证 if rules.Prefix != nil && !strings.HasPrefix(value, *rules.Prefix) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidValue, fmt.Sprintf("字段 %s 必须以 '%s' 开头", fieldPath, *rules.Prefix), )) } if rules.Suffix != nil && !strings.HasSuffix(value, *rules.Suffix) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidValue, fmt.Sprintf("字段 %s 必须以 '%s' 结尾", fieldPath, *rules.Suffix), )) } // 包含/不包含验证 if rules.Contains != nil && !strings.Contains(value, *rules.Contains) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidValue, fmt.Sprintf("字段 %s 必须包含 '%s'", fieldPath, *rules.Contains), )) } if rules.NotContains != nil && strings.Contains(value, *rules.NotContains) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidValue, fmt.Sprintf("字段 %s 不能包含 '%s'", fieldPath, *rules.NotContains), )) } // in 验证 if len(rules.In) > 0 { found := false for _, allowed := range rules.In { if value == allowed { found = true break } } if !found { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidValue, fmt.Sprintf("字段 %s 的值不在允许的列表中", fieldPath), )) } } // not_in 验证 if len(rules.NotIn) > 0 { for _, disallowed := range rules.NotIn { if value == disallowed { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidValue, fmt.Sprintf("字段 %s 的值在禁止列表中", fieldPath), )) break } } } // Well-known 格式验证 switch rules.WellKnown.(type) { case *validate.StringRules_Email: if !isValidEmail(value) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidFormat, fmt.Sprintf("字段 %s 不是有效的邮箱地址", fieldPath), )) } case *validate.StringRules_Uuid: if !isValidUUID(value) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidFormat, fmt.Sprintf("字段 %s 不是有效的 UUID", fieldPath), )) } case *validate.StringRules_Uri: if !isValidURI(value) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidFormat, fmt.Sprintf("字段 %s 不是有效的 URI", fieldPath), )) } case *validate.StringRules_ChineseMobile: if !isValidChineseMobile(value) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidFormat, fmt.Sprintf("字段 %s 不是有效的中国手机号", fieldPath), )) } case *validate.StringRules_ChineseIdCard: if !isValidChineseIDCard(value) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidFormat, fmt.Sprintf("字段 %s 不是有效的中国身份证号", fieldPath), )) } case *validate.StringRules_ChineseName: if !isValidChineseName(value) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidFormat, fmt.Sprintf("字段 %s 不是有效的中文姓名", fieldPath), )) } case *validate.StringRules_ChinesePostcode: if !isValidChinesePostcode(value) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidFormat, fmt.Sprintf("字段 %s 不是有效的中国邮政编码", fieldPath), )) } } // 字符类型验证 if rules.Numeric != nil && *rules.Numeric { if !regexp.MustCompile(`^\d+$`).MatchString(value) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidFormat, fmt.Sprintf("字段 %s 必须只包含数字", fieldPath), )) } } if rules.Alpha != nil && *rules.Alpha { if !regexp.MustCompile(`^[a-zA-Z]+$`).MatchString(value) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidFormat, fmt.Sprintf("字段 %s 必须只包含字母", fieldPath), )) } } if rules.Alphanumeric != nil && *rules.Alphanumeric { if !regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString(value) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidFormat, fmt.Sprintf("字段 %s 必须只包含字母和数字", fieldPath), )) } } // 大小写验证 if rules.Lowercase != nil && *rules.Lowercase { if value != strings.ToLower(value) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidFormat, fmt.Sprintf("字段 %s 必须是小写", fieldPath), )) } } if rules.Uppercase != nil && *rules.Uppercase { if value != strings.ToUpper(value) { errors.Add(NewValidationError( fieldPath, ErrCodeInvalidFormat, fmt.Sprintf("字段 %s 必须是大写", fieldPath), )) } } return nil } // Helper functions for format validation func isValidEmail(email string) bool { // 简化的邮箱验证正则 pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` matched, _ := regexp.MatchString(pattern, email) return matched } func isValidUUID(uuid string) bool { pattern := `^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$` matched, _ := regexp.MatchString(pattern, uuid) return matched } func isValidURI(uri string) bool { // 简化的 URI 验证 return strings.Contains(uri, "://") || strings.HasPrefix(uri, "/") }