go-playground/validator/v10校验:邮箱/手机号至少必填其一(自定义校验panic)
用go-playground/validator/v10实现Gorm User模型的邮箱/手机号二选一校验
两种实现方案:内置标签(推荐)/自定义校验器
方案一:使用内置标签(零自定义,避免panic)
直接利用validator内置的required_without_all标签,结合email和e164格式校验标签,就能满足需求,完全不需要写自定义校验函数,从根源避免panic。
定义User模型
import ( "gorm.io/gorm" "github.com/go-playground/validator/v10" ) // 非指针字段版本 type User struct { gorm.Model Email string `gorm:"type:varchar(100)" validate:"email,required_without_all=Phone"` Phone string `gorm:"type:varchar(20)" validate:"e164,required_without_all=Email"` } // 指针字段版本(Gorm常用,处理零值更友好) type User struct { gorm.Model Email *string `gorm:"type:varchar(100)" validate:"email,required_without_all=Phone"` Phone *string `gorm:"type:varchar(20)" validate:"e164,required_without_all=Email"` }
required_without_all=Phone:当Phone字段为空时,Email必须非空;反之同理email:校验邮箱格式合法性e164:校验手机号符合E.164标准(比如+8613800138000)
校验示例
func main() { validate := validator.New() // 测试:两个字段都为空 → 校验失败 user1 := User{} if err := validate.Struct(user1); err != nil { println(err.Error()) // 会提示Email或Phone必填 } // 测试:邮箱格式正确 → 校验通过 validEmail := "user@example.com" user2 := User{Email: &validEmail} if err := validate.Struct(user2); err == nil { println("校验通过") } // 测试:手机号格式错误 → 校验失败 invalidPhone := "13800138000" // 缺少+86前缀,不符合E.164 user3 := User{Phone: &invalidPhone} if err := validate.Struct(user3); err != nil { println(err.Error()) // 提示手机号格式错误 } }
方案二:自定义校验器(适合复杂场景)
如果必须用自定义校验,要注意避免panic的核心细节:类型断言必须检查结果、处理指针字段空值、禁止在函数内panic。
1. 定义带指针字段的User模型
type User struct { gorm.Model Email *string `gorm:"type:varchar(100)" validate:"email"` Phone *string `gorm:"type:varchar(20)" validate:"e164"` }
2. 安全的自定义校验函数
// 校验规则:Email和Phone至少填写一个 func validateEmailOrPhone(fl validator.FieldLevel) bool { // 断言父结构体为User类型,必须检查ok值,否则会panic user, ok := fl.Parent().Interface().(User) if !ok { return false // 类型不匹配,校验不通过 } // 处理指针字段的空值判断:先判nil,再判空字符串 emailEmpty := user.Email == nil || *user.Email == "" phoneEmpty := user.Phone == nil || *user.Phone == "" // 两个都为空则校验不通过 return !(emailEmpty && phoneEmpty) }
3. 注册校验器并绑定到结构体
func main() { validate := validator.New() // 注册自定义校验器,绑定到自定义标签"email_or_phone" if err := validate.RegisterValidation("email_or_phone", validateEmailOrPhone); err != nil { panic(err) // 注册失败才panic,这里是初始化阶段,可控 } // 给User结构体添加自定义校验标签(结构体级别校验) type User struct { gorm.Model Email *string `gorm:"type:varchar(100)" validate:"email"` Phone *string `gorm:"type:varchar(20)" validate:"e164"` } `validate:"email_or_phone"` // 后续校验逻辑同方案一 }
避免自定义校验panic的关键要点
- 类型断言必须加ok判断:永远不要直接
user := fl.Parent().Interface().(User),否则传入非User结构体时会直接panic。 - 指针字段必须先判nil:Gorm模型常用指针字段处理零值,直接取值会触发空指针panic,必须先检查
user.Email == nil。 - 校验函数内禁止panic:所有异常情况都返回
false,让validator统一处理错误信息,不要在函数内抛出panic。
内容的提问来源于stack exchange,提问作者Arul Ranjith
相关产品推荐
相关产品推荐

