930 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			930 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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)
 | ||
| 
 | ||
| 	// 企业名称验证器
 | ||
| 	validate.RegisterValidation("enterprise_name", validateEnterpriseName)
 | ||
| 	validate.RegisterValidation("validEnterpriseName", validateEnterpriseName)
 | ||
| }
 | ||
| 
 | ||
| // 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
 | ||
| }
 | ||
| 
 | ||
| // 自定义身份证校验(增强版)
 | ||
| // 校验规则:
 | ||
| // 1. 格式:18位,前6位地区码(首位不为0),7-14位出生日期,15-17位顺序码,18位校验码
 | ||
| // 2. 出生日期必须合法(验证年月日有效性,包括闰年)
 | ||
| // 3. 校验码按照GB 11643-1999标准计算验证
 | ||
| func validateIDCard(fl validator.FieldLevel) bool {
 | ||
| 	idCard := fl.Field().String()
 | ||
| 
 | ||
| 	// 1. 基本格式验证:地区码(6位) + 年(4位) + 月(2位) + 日(2位) + 顺序码(3位) + 校验码(1位)
 | ||
| 	validIDPattern := `^[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]$`
 | ||
| 	matched, _ := regexp.MatchString(validIDPattern, idCard)
 | ||
| 	if !matched {
 | ||
| 		return false
 | ||
| 	}
 | ||
| 
 | ||
| 	// 2. 验证出生日期的合法性
 | ||
| 	year, _ := strconv.Atoi(idCard[6:10])
 | ||
| 	month, _ := strconv.Atoi(idCard[10:12])
 | ||
| 	day, _ := strconv.Atoi(idCard[12:14])
 | ||
| 
 | ||
| 	// 构造日期并验证是否合法(time包会自动处理闰年等情况)
 | ||
| 	birthDate := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
 | ||
| 	if birthDate.Year() != year || int(birthDate.Month()) != month || birthDate.Day() != day {
 | ||
| 		return false // 日期不合法,如2月30日、4月31日等
 | ||
| 	}
 | ||
| 
 | ||
| 	// 3. 验证校验码(按照GB 11643-1999标准)
 | ||
| 	return validateIDCardChecksum(idCard)
 | ||
| }
 | ||
| 
 | ||
| // 验证身份证校验码(GB 11643-1999标准)
 | ||
| func validateIDCardChecksum(idCard string) bool {
 | ||
| 	// 加权因子
 | ||
| 	weights := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
 | ||
| 	// 校验码对应值
 | ||
| 	checksumChars := []byte{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'}
 | ||
| 
 | ||
| 	sum := 0
 | ||
| 	for i := 0; i < 17; i++ {
 | ||
| 		num := int(idCard[i] - '0')
 | ||
| 		sum += num * weights[i]
 | ||
| 	}
 | ||
| 
 | ||
| 	// 计算校验码
 | ||
| 	checksum := checksumChars[sum%11]
 | ||
| 	lastChar := idCard[17]
 | ||
| 
 | ||
| 	// 支持小写x
 | ||
| 	if lastChar == 'x' {
 | ||
| 		lastChar = 'X'
 | ||
| 	}
 | ||
| 
 | ||
| 	return byte(lastChar) == checksum
 | ||
| }
 | ||
| // 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
 | ||
| }
 | ||
| 
 | ||
| // validateEnterpriseName 企业名称验证器
 | ||
| func validateEnterpriseName(fl validator.FieldLevel) bool {
 | ||
| 	enterpriseName := fl.Field().String()
 | ||
| 	
 | ||
| 	// 去除首尾空格
 | ||
| 	trimmedName := strings.TrimSpace(enterpriseName)
 | ||
| 	if trimmedName == "" {
 | ||
| 		return false
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 长度验证:2-100个字符
 | ||
| 	if len(trimmedName) < 2 || len(trimmedName) > 100 {
 | ||
| 		return false
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 检查是否包含非法字符(允许中英文括号)
 | ||
| 	invalidChars := []string{
 | ||
| 		"`", "~", "!", "@", "#", "$", "%", "^", "&", "*",
 | ||
| 		"+", "=", "{", "}", "[", "]", "\\", "|", ";", ":", "'", "\"", "<", ">", ",", ".", "?", "/",
 | ||
| 	}
 | ||
| 	for _, char := range invalidChars {
 | ||
| 		if strings.Contains(trimmedName, char) {
 | ||
| 			return false
 | ||
| 		}
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 必须包含至少一个中文字符或英文字母
 | ||
| 	hasValidChar := regexp.MustCompile(`[\p{Han}a-zA-Z]`).MatchString(trimmedName)
 | ||
| 	if !hasValidChar {
 | ||
| 		return false
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 验证企业名称的基本格式(支持各种类型的企业)
 | ||
| 	// 支持:有限公司、股份有限公司、工作室、个体工商户、合伙企业等
 | ||
| 	validSuffixes := []string{
 | ||
| 		"有限公司", "有限责任公司", "股份有限公司", "股份公司",
 | ||
| 		"工作室", "个体工商户", "个人独资企业", "合伙企业",
 | ||
| 		"集团有限公司", "集团股份有限公司",
 | ||
| 		"分公司", "子公司", "办事处", "代表处",
 | ||
| 		"Co.,Ltd", "Co., Ltd", "Ltd", "LLC", "Inc", "Corp",
 | ||
| 		"Company", "Studio", "Workshop", "Enterprise",
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 检查是否以合法的企业类型结尾(不强制要求,因为有些企业名称可能没有标准后缀)
 | ||
| 	// 但如果有后缀,必须是合法的
 | ||
| 	hasValidSuffix := false
 | ||
| 	for _, suffix := range validSuffixes {
 | ||
| 		if strings.HasSuffix(trimmedName, suffix) {
 | ||
| 			hasValidSuffix = true
 | ||
| 			break
 | ||
| 		}
 | ||
| 		// 同时检查括号内的企业类型,如:(个体工商户)、(分公司)
 | ||
| 		if strings.HasSuffix(trimmedName, "("+suffix+")") {
 | ||
| 			hasValidSuffix = true
 | ||
| 			break
 | ||
| 		}
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 如果名称中包含常见的企业类型关键词,则必须是合法的后缀
 | ||
| 	enterpriseKeywords := []string{"公司", "工作室", "企业", "集团", "Co", "Ltd", "LLC", "Inc", "Corp", "Company", "Studio", "Workshop", "Enterprise"}
 | ||
| 	containsKeyword := false
 | ||
| 	for _, keyword := range enterpriseKeywords {
 | ||
| 		if strings.Contains(trimmedName, keyword) {
 | ||
| 			containsKeyword = true
 | ||
| 			break
 | ||
| 		}
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 如果包含企业关键词但没有合法后缀,则验证失败
 | ||
| 	if containsKeyword && !hasValidSuffix {
 | ||
| 		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
 | ||
| }
 | ||
| 
 | ||
| // ValidateEnterpriseName 验证企业名称
 | ||
| func ValidateEnterpriseName(enterpriseName string) error {
 | ||
| 	if enterpriseName == "" {
 | ||
| 		return fmt.Errorf("企业名称不能为空")
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 去除首尾空格
 | ||
| 	trimmedName := strings.TrimSpace(enterpriseName)
 | ||
| 	if trimmedName == "" {
 | ||
| 		return fmt.Errorf("企业名称不能为空")
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 长度验证:2-100个字符
 | ||
| 	if len(trimmedName) < 2 {
 | ||
| 		return fmt.Errorf("企业名称长度不能少于2个字符")
 | ||
| 	}
 | ||
| 	if len(trimmedName) > 100 {
 | ||
| 		return fmt.Errorf("企业名称长度不能超过100个字符")
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 检查是否包含非法字符(允许中英文括号)
 | ||
| 	invalidChars := []string{
 | ||
| 		"`", "~", "!", "@", "#", "$", "%", "^", "&", "*",
 | ||
| 		"+", "=", "{", "}", "[", "]", "\\", "|", ";", ":", "'", "\"", "<", ">", ",", ".", "?", "/",
 | ||
| 	}
 | ||
| 	for _, char := range invalidChars {
 | ||
| 		if strings.Contains(trimmedName, char) {
 | ||
| 			return fmt.Errorf("企业名称不能包含特殊字符: %s", char)
 | ||
| 		}
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 必须包含至少一个中文字符或英文字母
 | ||
| 	hasValidChar := regexp.MustCompile(`[\p{Han}a-zA-Z]`).MatchString(trimmedName)
 | ||
| 	if !hasValidChar {
 | ||
| 		return fmt.Errorf("企业名称必须包含中文字符或英文字母")
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 验证企业名称的基本格式(支持各种类型的企业)
 | ||
| 	// 支持:有限公司、股份有限公司、工作室、个体工商户、合伙企业等
 | ||
| 	validSuffixes := []string{
 | ||
| 		"有限公司", "有限责任公司", "股份有限公司", "股份公司",
 | ||
| 		"工作室", "个体工商户", "个人独资企业", "合伙企业",
 | ||
| 		"集团有限公司", "集团股份有限公司",
 | ||
| 		"分公司", "子公司", "办事处", "代表处",
 | ||
| 		"Co.,Ltd", "Co., Ltd", "Ltd", "LLC", "Inc", "Corp",
 | ||
| 		"Company", "Studio", "Workshop", "Enterprise",
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 检查是否以合法的企业类型结尾
 | ||
| 	hasValidSuffix := false
 | ||
| 	for _, suffix := range validSuffixes {
 | ||
| 		if strings.HasSuffix(trimmedName, suffix) {
 | ||
| 			hasValidSuffix = true
 | ||
| 			break
 | ||
| 		}
 | ||
| 		// 同时检查括号内的企业类型,如:(个体工商户)、(分公司)
 | ||
| 		if strings.HasSuffix(trimmedName, "("+suffix+")") {
 | ||
| 			hasValidSuffix = true
 | ||
| 			break
 | ||
| 		}
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 如果名称中包含常见的企业类型关键词,则必须是合法的后缀
 | ||
| 	enterpriseKeywords := []string{"公司", "工作室", "企业", "集团", "Co", "Ltd", "LLC", "Inc", "Corp", "Company", "Studio", "Workshop", "Enterprise"}
 | ||
| 	containsKeyword := false
 | ||
| 	for _, keyword := range enterpriseKeywords {
 | ||
| 		if strings.Contains(trimmedName, keyword) {
 | ||
| 			containsKeyword = true
 | ||
| 			break
 | ||
| 		}
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 如果包含企业关键词但没有合法后缀,则验证失败
 | ||
| 	if containsKeyword && !hasValidSuffix {
 | ||
| 		return fmt.Errorf("企业名称格式不正确,请使用标准的企业类型后缀(如:有限公司、工作室等)")
 | ||
| 	}
 | ||
| 	
 | ||
| 	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)
 | ||
| }
 |