Files
tyapi-server/internal/shared/validator/custom_validators.go

491 lines
13 KiB
Go
Raw Normal View History

2025-07-20 20:53:26 +08:00
package validator
import (
2025-07-28 01:46:39 +08:00
"fmt"
2025-07-20 20:53:26 +08:00
"net/url"
"regexp"
2025-07-28 01:46:39 +08:00
"strconv"
"strings"
"time"
2025-07-20 20:53:26 +08:00
"github.com/go-playground/validator/v10"
)
// RegisterCustomValidators 注册所有自定义验证器
func RegisterCustomValidators(validate *validator.Validate) {
// 手机号验证器
validate.RegisterValidation("phone", validatePhone)
2025-08-26 14:43:27 +08:00
2025-07-20 20:53:26 +08:00
// 用户名验证器字母开头允许字母数字下划线3-20位
validate.RegisterValidation("username", validateUsername)
2025-08-26 14:43:27 +08:00
2025-07-20 20:53:26 +08:00
// 强密码验证器至少8位包含大小写字母和数字
validate.RegisterValidation("strong_password", validateStrongPassword)
2025-08-26 14:43:27 +08:00
2025-07-20 20:53:26 +08:00
// 统一社会信用代码验证器
validate.RegisterValidation("social_credit_code", validateSocialCreditCode)
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 姓名验证器不能为空字符串长度1-50字符
validate.RegisterValidation("validName", validateName)
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 身份证号验证器(兼容两种标签)
validate.RegisterValidation("validIDCard", validateIDCard)
2025-07-20 20:53:26 +08:00
validate.RegisterValidation("id_card", validateIDCard)
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 统一社会信用代码验证器
validate.RegisterValidation("validUSCI", validateUSCI)
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 手机号验证器
validate.RegisterValidation("validMobileNo", validateMobileNo)
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 手机类型验证器
validate.RegisterValidation("validMobileType", validateMobileType)
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 日期验证器
validate.RegisterValidation("validDate", validateDate)
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 时间范围验证器
validate.RegisterValidation("validTimeRange", validateTimeRange)
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 银行卡验证器
validate.RegisterValidation("validBankCard", validateBankCard)
2025-08-26 14:43:27 +08:00
2025-07-20 20:53:26 +08:00
// 价格验证器(非负数)
validate.RegisterValidation("price", validatePrice)
2025-08-26 14:43:27 +08:00
2025-07-20 20:53:26 +08:00
// 排序方向验证器
validate.RegisterValidation("sort_order", validateSortOrder)
2025-08-26 14:43:27 +08:00
2025-07-20 20:53:26 +08:00
// 产品代码验证器字母数字下划线连字符3-50位
validate.RegisterValidation("product_code", validateProductCode)
2025-08-26 14:43:27 +08:00
2025-07-20 20:53:26 +08:00
// UUID验证器
validate.RegisterValidation("uuid", validateUUID)
2025-08-26 14:43:27 +08:00
2025-07-20 20:53:26 +08:00
// URL验证器
validate.RegisterValidation("url", validateURL)
2025-08-26 14:43:27 +08:00
2025-07-28 01:46:39 +08:00
// 企业邮箱验证器
validate.RegisterValidation("enterprise_email", validateEnterpriseEmail)
2025-08-26 14:43:27 +08:00
2025-07-28 01:46:39 +08:00
// 企业地址验证器
validate.RegisterValidation("enterprise_address", validateEnterpriseAddress)
2025-08-26 14:43:27 +08:00
2025-07-28 01:46:39 +08:00
// IP地址验证器
validate.RegisterValidation("ip", validateIP)
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 非空字符串验证器(不能为空字符串或只包含空格)
validate.RegisterValidation("notEmpty", validateNotEmpty)
2025-08-26 14:43:27 +08:00
2025-07-28 01:46:39 +08:00
// 授权日期验证器
validate.RegisterValidation("auth_date", validateAuthDate)
2025-08-26 14:43:27 +08:00
// 授权书URL验证器
validate.RegisterValidation("authorization_url", validateAuthorizationURL)
// 唯一标识验证器小于等于32位字符串
validate.RegisterValidation("validUniqueID", validateUniqueID)
// 回调地址验证器
validate.RegisterValidation("validReturnURL", validateReturnURL)
2025-07-20 20:53:26 +08:00
}
// 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
2025-07-28 01:46:39 +08:00
}
// 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
2025-08-18 14:13:16 +08:00
}
// 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
2025-08-26 14:43:27 +08:00
}
2025-07-28 01:46:39 +08:00
// 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)
2025-08-26 14:43:27 +08:00
2025-07-28 01:46:39 +08:00
// 检查解析后的日期是否与输入一致防止无效日期如20230230
expectedDateStr := date.Format("20060102")
if expectedDateStr != dateStr {
return time.Time{}, fmt.Errorf("无效日期")
}
return date, nil
2025-08-18 14:13:16 +08:00
}
// 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
}
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 尝试解析日期
_, 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标签处理
}
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 时间范围格式HH:MM-HH:MM
parts := strings.Split(timeRange, "-")
if len(parts) != 2 {
return false
}
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
startTime := parts[0]
endTime := parts[1]
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 检查时间格式HH:MM
timePattern := `^([01]?[0-9]|2[0-3]):[0-5][0-9]$`
startMatched, _ := regexp.MatchString(timePattern, startTime)
endMatched, _ := regexp.MatchString(timePattern, endTime)
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
if !startMatched || !endMatched {
return false
}
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 检查开始时间不能晚于结束时间
start, _ := time.Parse("15:04", startTime)
end, _ := time.Parse("15:04", endTime)
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
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
}
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 使用Luhn算法验证银行卡号
return validateLuhn(bankCard)
}
// validateLuhn Luhn算法验证银行卡号
func validateLuhn(cardNumber string) bool {
sum := 0
alternate := false
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
// 从右到左遍历
for i := len(cardNumber) - 1; i >= 0; i-- {
digit, err := strconv.Atoi(string(cardNumber[i]))
if err != nil {
return false
}
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
if alternate {
digit *= 2
if digit > 9 {
digit = digit%10 + digit/10
}
}
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
sum += digit
alternate = !alternate
}
2025-08-26 14:43:27 +08:00
2025-08-18 14:13:16 +08:00
return sum%10 == 0
2025-08-26 14:43:27 +08:00
}
// 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
}