You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的关键要点

  1. 类型断言必须加ok判断:永远不要直接user := fl.Parent().Interface().(User),否则传入非User结构体时会直接panic。
  2. 指针字段必须先判nil:Gorm模型常用指针字段处理零值,直接取值会触发空指针panic,必须先检查user.Email == nil。
  3. 校验函数内禁止panic:所有异常情况都返回false,让validator统一处理错误信息,不要在函数内抛出panic。

内容的提问来源于stack exchange,提问作者Arul Ranjith

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 23:30:41