package validator import ( "fmt" "net/url" "regexp" "strconv" "strings" "time" "github.com/go-playground/validator/v10" ) // RegisterCustomValidators 注册所有自定义验证器 func RegisterCustomValidators(validate *validator.Validate) { // 手机号验证器 validate.RegisterValidation("phone", validatePhone) // 用户名验证器(字母开头,允许字母数字下划线,3-20位) validate.RegisterValidation("username", validateUsername) // 强密码验证器(至少8位,包含大小写字母和数字) validate.RegisterValidation("strong_password", validateStrongPassword) // 统一社会信用代码验证器 validate.RegisterValidation("social_credit_code", validateSocialCreditCode) // 姓名验证器(不能为空字符串,长度1-50字符) validate.RegisterValidation("validName", validateName) // 身份证号验证器(兼容两种标签) validate.RegisterValidation("validIDCard", validateIDCard) validate.RegisterValidation("id_card", validateIDCard) // 统一社会信用代码验证器 validate.RegisterValidation("validUSCI", validateUSCI) // 手机号验证器 validate.RegisterValidation("validMobileNo", validateMobileNo) // 手机类型验证器 validate.RegisterValidation("validMobileType", validateMobileType) // 日期验证器 validate.RegisterValidation("validDate", validateDate) // 时间范围验证器 validate.RegisterValidation("validTimeRange", validateTimeRange) // 银行卡验证器 validate.RegisterValidation("validBankCard", validateBankCard) // 价格验证器(非负数) validate.RegisterValidation("price", validatePrice) // 排序方向验证器 validate.RegisterValidation("sort_order", validateSortOrder) // 产品代码验证器(字母数字下划线连字符,3-50位) validate.RegisterValidation("product_code", validateProductCode) // UUID验证器 validate.RegisterValidation("uuid", validateUUID) // URL验证器 validate.RegisterValidation("url", validateURL) // 企业邮箱验证器 validate.RegisterValidation("enterprise_email", validateEnterpriseEmail) // 企业地址验证器 validate.RegisterValidation("enterprise_address", validateEnterpriseAddress) // IP地址验证器 validate.RegisterValidation("ip", validateIP) // 非空字符串验证器(不能为空字符串或只包含空格) validate.RegisterValidation("notEmpty", validateNotEmpty) // 授权日期验证器 validate.RegisterValidation("auth_date", validateAuthDate) validate.RegisterValidation("validAuthDate", validateAuthDate) // 授权书URL验证器 validate.RegisterValidation("authorization_url", validateAuthorizationURL) // 唯一标识验证器(小于等于32位字符串) validate.RegisterValidation("validUniqueID", validateUniqueID) // 回调地址验证器 validate.RegisterValidation("validReturnURL", validateReturnURL) } // validatePhone 手机号验证 func validatePhone(fl validator.FieldLevel) bool { phone := fl.Field().String() matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone) return matched } // validateUsername 用户名验证 func validateUsername(fl validator.FieldLevel) bool { username := fl.Field().String() matched, _ := regexp.MatchString(`^[a-zA-Z][a-zA-Z0-9_]{2,19}$`, username) return matched } // validateStrongPassword 强密码验证 func validateStrongPassword(fl validator.FieldLevel) bool { password := fl.Field().String() if len(password) < 8 { return false } hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password) hasLower := regexp.MustCompile(`[a-z]`).MatchString(password) hasDigit := regexp.MustCompile(`\d`).MatchString(password) return hasUpper && hasLower && hasDigit } // validateSocialCreditCode 统一社会信用代码验证 func validateSocialCreditCode(fl validator.FieldLevel) bool { code := fl.Field().String() matched, _ := regexp.MatchString(`^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$`, code) return matched } // validateIDCard 身份证号验证 func validateIDCard(fl validator.FieldLevel) bool { idCard := fl.Field().String() matched, _ := regexp.MatchString(`^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[\dXx]$`, idCard) return matched } // validatePrice 价格验证 func validatePrice(fl validator.FieldLevel) bool { price := fl.Field().Float() return price >= 0 } // validateSortOrder 排序方向验证 func validateSortOrder(fl validator.FieldLevel) bool { sortOrder := fl.Field().String() return sortOrder == "" || sortOrder == "asc" || sortOrder == "desc" } // validateProductCode 产品代码验证 func validateProductCode(fl validator.FieldLevel) bool { code := fl.Field().String() matched, _ := regexp.MatchString(`^[a-zA-Z0-9_-]{3,50}$`, code) return matched } // validateUUID UUID验证 func validateUUID(fl validator.FieldLevel) bool { uuid := fl.Field().String() matched, _ := regexp.MatchString(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, uuid) return matched } // validateURL URL验证 func validateURL(fl validator.FieldLevel) bool { urlStr := fl.Field().String() _, err := url.ParseRequestURI(urlStr) return err == nil } // validateEnterpriseEmail 企业邮箱验证 func validateEnterpriseEmail(fl validator.FieldLevel) bool { email := fl.Field().String() // 邮箱格式验证:用户名@域名.顶级域名 matched, _ := regexp.MatchString(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`, email) return matched } // validateEnterpriseAddress 企业地址验证 func validateEnterpriseAddress(fl validator.FieldLevel) bool { address := fl.Field().String() // 地址长度验证:2-200字符,不能只包含空格 if len(strings.TrimSpace(address)) < 2 || len(address) > 200 { return false } // 地址不能只包含特殊字符 matched, _ := regexp.MatchString(`^[^\s]+.*[^\s]+$`, strings.TrimSpace(address)) return matched } // validateIP IP地址验证(支持IPv4) func validateIP(fl validator.FieldLevel) bool { ip := fl.Field().String() // 使用正则表达式验证IPv4格式 pattern := `^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$` matched, _ := regexp.MatchString(pattern, ip) return matched } // validateName 姓名验证器 func validateName(fl validator.FieldLevel) bool { name := fl.Field().String() // 去除首尾空格后检查长度 trimmedName := strings.TrimSpace(name) if len(trimmedName) < 1 || len(trimmedName) > 50 { return false } // 姓名不能只包含空格或特殊字符 // 必须包含至少一个中文字符或英文字母 hasValidChar := regexp.MustCompile(`[\p{Han}a-zA-Z]`).MatchString(trimmedName) return hasValidChar } // validateAuthDate 授权日期验证器 // 格式:YYYYMMDD-YYYYMMDD,之前的日期范围必须包括今天 func validateAuthDate(fl validator.FieldLevel) bool { authDate := fl.Field().String() if authDate == "" { return true // 空值由required标签处理 } // 检查格式:YYYYMMDD-YYYYMMDD parts := strings.Split(authDate, "-") if len(parts) != 2 { return false } startDateStr := parts[0] endDateStr := parts[1] // 检查日期格式是否为8位数字 if len(startDateStr) != 8 || len(endDateStr) != 8 { return false } // 解析开始日期 startDate, err := parseYYYYMMDD(startDateStr) if err != nil { return false } // 解析结束日期 endDate, err := parseYYYYMMDD(endDateStr) if err != nil { return false } // 检查开始日期不能晚于结束日期 if startDate.After(endDate) { return false } // 获取今天的日期(去掉时间部分) today := time.Now().Truncate(24 * time.Hour) // 检查日期范围是否包括今天 // 如果两个日期都是今天也行 return !startDate.After(today) && !endDate.Before(today) } // parseYYYYMMDD 解析YYYYMMDD格式的日期字符串 func parseYYYYMMDD(dateStr string) (time.Time, error) { if len(dateStr) != 8 { return time.Time{}, fmt.Errorf("日期格式错误") } year, err := strconv.Atoi(dateStr[:4]) if err != nil { return time.Time{}, err } month, err := strconv.Atoi(dateStr[4:6]) if err != nil { return time.Time{}, err } day, err := strconv.Atoi(dateStr[6:8]) if err != nil { return time.Time{}, err } // 验证日期有效性 date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) // 检查解析后的日期是否与输入一致(防止无效日期如20230230) expectedDateStr := date.Format("20060102") if expectedDateStr != dateStr { return time.Time{}, fmt.Errorf("无效日期") } return date, nil } // validateUSCI 统一社会信用代码验证器 func validateUSCI(fl validator.FieldLevel) bool { usci := fl.Field().String() // 统一社会信用代码格式:18位,由数字和大写字母组成 // 格式:1位登记管理部门代码 + 1位机构类别代码 + 6位登记管理机关行政区划码 + 9位主体标识码 + 1位校验码 matched, _ := regexp.MatchString(`^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$`, usci) return matched } // validateMobileNo 手机号验证器 func validateMobileNo(fl validator.FieldLevel) bool { mobile := fl.Field().String() // 中国手机号格式:1开头,第二位是3-9,总共11位数字 matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, mobile) return matched } // validateMobileType 手机类型验证器 func validateMobileType(fl validator.FieldLevel) bool { mobileType := fl.Field().String() // 手机类型:移动、联通、电信等 validTypes := []string{"移动", "联通", "电信", "广电", "虚拟运营商"} for _, validType := range validTypes { if mobileType == validType { return true } } return false } // validateDate 日期验证器 func validateDate(fl validator.FieldLevel) bool { dateStr := fl.Field().String() // 检查日期格式:YYYY-MM-DD matched, _ := regexp.MatchString(`^\d{4}-\d{2}-\d{2}$`, dateStr) if !matched { return false } // 尝试解析日期 _, err := time.Parse("2006-01-02", dateStr) return err == nil } // validateTimeRange 时间范围验证器 func validateTimeRange(fl validator.FieldLevel) bool { timeRange := fl.Field().String() if timeRange == "" { return true // 空值由omitempty标签处理 } // 时间范围格式:HH:MM-HH:MM parts := strings.Split(timeRange, "-") if len(parts) != 2 { return false } startTime := parts[0] endTime := parts[1] // 检查时间格式:HH:MM timePattern := `^([01]?[0-9]|2[0-3]):[0-5][0-9]$` startMatched, _ := regexp.MatchString(timePattern, startTime) endMatched, _ := regexp.MatchString(timePattern, endTime) if !startMatched || !endMatched { return false } // 检查开始时间不能晚于结束时间 start, _ := time.Parse("15:04", startTime) end, _ := time.Parse("15:04", endTime) return start.Before(end) || start.Equal(end) } // validateNotEmpty 非空字符串验证器 func validateNotEmpty(fl validator.FieldLevel) bool { value := fl.Field().String() // 去除首尾空格后检查是否为空 trimmedValue := strings.TrimSpace(value) return len(trimmedValue) > 0 } // validateBankCard 银行卡验证器 func validateBankCard(fl validator.FieldLevel) bool { bankCard := fl.Field().String() // 银行卡号格式:13-19位数字 matched, _ := regexp.MatchString(`^\d{13,19}$`, bankCard) if !matched { return false } // 使用Luhn算法验证银行卡号 return validateLuhn(bankCard) } // validateLuhn Luhn算法验证银行卡号 func validateLuhn(cardNumber string) bool { sum := 0 alternate := false // 从右到左遍历 for i := len(cardNumber) - 1; i >= 0; i-- { digit, err := strconv.Atoi(string(cardNumber[i])) if err != nil { return false } if alternate { digit *= 2 if digit > 9 { digit = digit%10 + digit/10 } } sum += digit alternate = !alternate } return sum%10 == 0 } // validateAuthorizationURL 授权书URL验证器 func validateAuthorizationURL(fl validator.FieldLevel) bool { urlStr := fl.Field().String() if urlStr == "" { return true // 空值由required标签处理 } // 解析URL parsedURL, err := url.Parse(urlStr) if err != nil { return false } // 检查协议 if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { return false } // 检查文件扩展名 path := parsedURL.Path validExtensions := []string{".pdf", ".jpg", ".jpeg", ".png", ".bmp"} hasValidExtension := false for _, ext := range validExtensions { if strings.HasSuffix(strings.ToLower(path), ext) { hasValidExtension = true break } } return hasValidExtension } // validateUniqueID 唯一标识验证器(小于等于32位字符串) func validateUniqueID(fl validator.FieldLevel) bool { uniqueID := fl.Field().String() if uniqueID == "" { return true // 空值由required标签处理 } // 检查长度:小于等于32位 if len(uniqueID) > 32 { return false } // 检查是否只包含允许的字符:字母、数字、下划线、连字符 matched, _ := regexp.MatchString(`^[a-zA-Z0-9_-]+$`, uniqueID) return matched } // validateReturnURL 回调地址验证器 func validateReturnURL(fl validator.FieldLevel) bool { returnURL := fl.Field().String() if returnURL == "" { return true // 空值由required标签处理 } // 检查长度:不能超过500字符 if len(returnURL) > 500 { return false } // 检查URL格式 parsedURL, err := url.Parse(returnURL) if err != nil { return false } // 检查协议:只允许http和https if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { return false } // 检查是否有域名 if parsedURL.Host == "" { return false } return true } // ================ 统一的业务校验方法 ================ // ValidatePhone 验证手机号 func ValidatePhone(phone string) error { if phone == "" { return fmt.Errorf("手机号不能为空") } matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone) if !matched { return fmt.Errorf("手机号格式不正确") } return nil } // ValidatePassword 验证密码强度 func ValidatePassword(password string) error { if password == "" { return fmt.Errorf("密码不能为空") } if len(password) < 8 { return fmt.Errorf("密码长度不能少于8位") } hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password) hasLower := regexp.MustCompile(`[a-z]`).MatchString(password) hasDigit := regexp.MustCompile(`\d`).MatchString(password) if !hasUpper { return fmt.Errorf("密码必须包含大写字母") } if !hasLower { return fmt.Errorf("密码必须包含小写字母") } if !hasDigit { return fmt.Errorf("密码必须包含数字") } return nil } // ValidateUsername 验证用户名 func ValidateUsername(username string) error { if username == "" { return fmt.Errorf("用户名不能为空") } matched, _ := regexp.MatchString(`^[a-zA-Z][a-zA-Z0-9_]{2,19}$`, username) if !matched { return fmt.Errorf("用户名格式不正确,只能包含字母、数字、下划线,且必须以字母开头,长度3-20位") } return nil } // ValidateSocialCreditCode 验证统一社会信用代码 func ValidateSocialCreditCode(code string) error { if code == "" { return fmt.Errorf("统一社会信用代码不能为空") } matched, _ := regexp.MatchString(`^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$`, code) if !matched { return fmt.Errorf("统一社会信用代码格式不正确,必须是18位统一社会信用代码") } return nil } // ValidateIDCard 验证身份证号 func ValidateIDCard(idCard string) error { if idCard == "" { return fmt.Errorf("身份证号不能为空") } matched, _ := regexp.MatchString(`^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[\dXx]$`, idCard) if !matched { return fmt.Errorf("身份证号格式不正确,必须是18位身份证号") } return nil } // ValidateUUID 验证UUID func ValidateUUID(uuid string) error { if uuid == "" { return fmt.Errorf("UUID不能为空") } matched, _ := regexp.MatchString(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, uuid) if !matched { return fmt.Errorf("UUID格式不正确") } return nil } // ValidateURL 验证URL func ValidateURL(urlStr string) error { if urlStr == "" { return fmt.Errorf("URL不能为空") } _, err := url.ParseRequestURI(urlStr) if err != nil { return fmt.Errorf("URL格式不正确: %v", err) } return nil } // ValidateProductCode 验证产品代码 func ValidateProductCode(code string) error { if code == "" { return fmt.Errorf("产品代码不能为空") } matched, _ := regexp.MatchString(`^[a-zA-Z0-9_\-\(\)()]{3,50}$`, code) if !matched { return fmt.Errorf("产品代码格式不正确,只能包含字母、数字、下划线、连字符、中英文括号,长度3-50位") } return nil } // ValidateEmail 验证邮箱 func ValidateEmail(email string) error { if email == "" { return fmt.Errorf("邮箱不能为空") } matched, _ := regexp.MatchString(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`, email) if !matched { return fmt.Errorf("邮箱格式不正确") } return nil } // ValidateSortOrder 验证排序方向 func ValidateSortOrder(sortOrder string) error { if sortOrder == "" { return nil // 允许为空 } if sortOrder != "asc" && sortOrder != "desc" { return fmt.Errorf("排序方向必须是 asc 或 desc") } return nil } // ValidatePrice 验证价格 func ValidatePrice(price float64) error { if price < 0 { return fmt.Errorf("价格不能为负数") } return nil } // ValidateStringLength 验证字符串长度 func ValidateStringLength(str string, fieldName string, min, max int) error { length := len(strings.TrimSpace(str)) if min > 0 && length < min { return fmt.Errorf("%s长度不能少于%d位", fieldName, min) } if max > 0 && length > max { return fmt.Errorf("%s长度不能超过%d位", fieldName, max) } return nil } // ValidateRequired 验证必填字段 func ValidateRequired(value interface{}, fieldName string) error { if value == nil { return fmt.Errorf("%s不能为空", fieldName) } switch v := value.(type) { case string: if strings.TrimSpace(v) == "" { return fmt.Errorf("%s不能为空", fieldName) } case *string: if v == nil || strings.TrimSpace(*v) == "" { return fmt.Errorf("%s不能为空", fieldName) } } return nil } // ValidateRange 验证数值范围 func ValidateRange(value float64, fieldName string, min, max float64) error { if value < min { return fmt.Errorf("%s不能小于%v", fieldName, min) } if value > max { return fmt.Errorf("%s不能大于%v", fieldName, max) } return nil } // ValidateSliceNotEmpty 验证切片不为空 func ValidateSliceNotEmpty(slice interface{}, fieldName string) error { if slice == nil { return fmt.Errorf("%s不能为空", fieldName) } switch v := slice.(type) { case []string: if len(v) == 0 { return fmt.Errorf("%s不能为空", fieldName) } case []int: if len(v) == 0 { return fmt.Errorf("%s不能为空", fieldName) } } return nil } // ================ 便捷的校验器创建函数 ================ // NewBusinessValidator 创建业务验证器(保持向后兼容) func NewBusinessValidator() *BusinessValidator { // 确保全局校验器已初始化 InitGlobalValidator() return &BusinessValidator{ validator: GetGlobalValidator(), // 使用全局校验器 } } // BusinessValidator 业务验证器(保持向后兼容) type BusinessValidator struct { validator *validator.Validate } // ValidateStruct 验证结构体 func (bv *BusinessValidator) ValidateStruct(data interface{}) error { return bv.validator.Struct(data) } // ValidateField 验证单个字段 func (bv *BusinessValidator) ValidateField(field interface{}, tag string) error { return bv.validator.Var(field, tag) }