Files
tyapi-server/internal/shared/validator/custom_validators.go
2025-07-28 01:46:39 +08:00

239 lines
6.7 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)
// 身份证号验证器
validate.RegisterValidation("id_card", validateIDCard)
// 价格验证器(非负数)
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("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
}
// 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
}