Files
tyapi-server/internal/shared/validator/custom_validators.go
2025-08-18 14:13:16 +08:00

401 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
// 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
}