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