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

Go语言User模型自定义验证报错:未定义phone验证函数

用户注册自定义验证panic问题排查

我在实现用户注册的自定义验证逻辑,要求手机号匹配指定格式,密码同时包含字母、特殊字符和数字,但运行代码时触发panic,报错信息如下:

2023/03/24 19:42:13 http: panic serving 127.0.0.1:52498: Undefined validation function 'phone' on field 'Phone'

实现代码:

package models

import (
    "errors"
    "fmt"
    "regexp"
    "strings"
    "time"

    "github.com/go-playground/validator/v10"
    "go.mongodb.org/mongo-driver/bson/primitive"
)

type User struct {
    UserId    primitive.ObjectID `bson:"_id"`
    F_name    *string            `json:"f_name" validate:"required"`
    L_name    *string            `json:"l_name" validate:"required"`
    Username  *string            `json:"username"`
    Phone     *string            `json:"phone" validate:"required,phone"`
    Email     *string            `json:"email" validate:"required,email"`
    Pronoun   *string            `json:"pronoun" validate:"required,eq=he_him|eq=she_her|eq=them_they"`
    Password  *string            `json:"password" validate:"required,min=8,containsLetter,containsSpecialChar"`
    Role      *string            `json:"role" validate:"required,eq=customer"`
    Verified  *bool              `json:"verified"`
    Active    *bool              `json:"active"`
    Avatar    *string            `json:"avatar" validate:"required"`
    Qrcode    *string            `json:"qrcode"`
    CreatedAt time.Time          `json:"createdAt"`
    UpdatedAt time.Time          `json:"updatedAt"`
}

func ValidateUser(user *User) error {
    validate := validator.New()
    validate.RegisterValidation("containsLetter", func(fl validator.FieldLevel) bool {
        return strings.ContainsAny(fl.Field().String(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
    })
    validate.RegisterValidation("containsSpecialChar", func(fl validator.FieldLevel) bool {
        re := regexp.MustCompile(`[!@#\$%\^&\*\(\)_\+\-=\[\]\{\}\|\;\:\,\.\/\<\>\?\\]`)
        return re.MatchString(fl.Field().String())
    })
    validate.RegisterValidation("phone", func(fl validator.FieldLevel) bool {
        re := regexp.MustCompile(`^\+\d{1,3}\-\d{9,10}$`)
        return re.MatchString(fl.Field().String())
    })
    if user.Verified == nil {
        user.Verified = new(bool)
        *user.Verified = false
    }
    if user.Active == nil {
        user.Active = new(bool)
        *user.Active = true
    }
    if err := validate.Struct(user); err != nil {
        var errMessages []string
        for _, err := range err.(validator.ValidationErrors) {
            errMessages = append(errMessages, fmt.Sprintf("field %s failed validation: %s", err.Field(), err.Tag()))
        }
        return errors.New(strings.Join(errMessages, ", "))
    }
    return nil
}

问题分析

  1. 核心panic原因:validator.RegisterValidation方法的返回值被忽略。该方法在注册自定义验证器失败时(如名称重复、参数非法)会返回error,但代码未处理这个错误,导致验证器注册失败后,validator找不到对应的phone验证函数,最终触发panic。
  2. 密码验证不完整:当前仅验证了密码包含字母和特殊字符,缺少数字检查,不符合需求。

修复方案

1. 处理验证器注册错误

每次调用RegisterValidation后必须检查返回的error,确保验证器注册成功,失败则直接返回错误。

2. 补全密码数字验证

新增containsNumber自定义验证器,并在Password字段的validate标签中添加该规则。

3. 优化正则表达式(可选)

简化特殊字符和手机号的正则,提升可读性。


修复后的代码

package models

import (
    "errors"
    "fmt"
    "regexp"
    "strings"
    "time"

    "github.com/go-playground/validator/v10"
    "go.mongodb.org/mongo-driver/bson/primitive"
)

type User struct {
    UserId    primitive.ObjectID `bson:"_id"`
    F_name    *string            `json:"f_name" validate:"required"`
    L_name    *string            `json:"l_name" validate:"required"`
    Username  *string            `json:"username"`
    Phone     *string            `json:"phone" validate:"required,phone"`
    Email     *string            `json:"email" validate:"required,email"`
    Pronoun   *string            `json:"pronoun" validate:"required,eq=he_him|eq=she_her|eq=them_they"`
    // 新增containsNumber验证规则
    Password  *string            `json:"password" validate:"required,min=8,containsLetter,containsNumber,containsSpecialChar"`
    Role      *string            `json:"role" validate:"required,eq=customer"`
    Verified  *bool              `json:"verified"`
    Active    *bool              `json:"active"`
    Avatar    *string            `json:"avatar" validate:"required"`
    Qrcode    *string            `json:"qrcode"`
    CreatedAt time.Time          `json:"createdAt"`
    UpdatedAt time.Time          `json:"updatedAt"`
}

func ValidateUser(user *User) error {
    validate := validator.New()

    // 注册containsLetter并检查错误
    if err := validate.RegisterValidation("containsLetter", func(fl validator.FieldLevel) bool {
        return strings.ContainsAny(fl.Field().String(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
    }); err != nil {
        return fmt.Errorf("failed to register containsLetter validator: %w", err)
    }

    // 注册containsNumber验证器
    if err := validate.RegisterValidation("containsNumber", func(fl validator.FieldLevel) bool {
        return strings.ContainsAny(fl.Field().String(), "0123456789")
    }); err != nil {
        return fmt.Errorf("failed to register containsNumber validator: %w", err)
    }

    // 注册containsSpecialChar并检查错误,简化正则
    if err := validate.RegisterValidation("containsSpecialChar", func(fl validator.FieldLevel) bool {
        re := regexp.MustCompile(`[!@#$%^&*()_+\-=\[\]{}|;:'",.<>?\\/]`)
        return re.MatchString(fl.Field().String())
    }); err != nil {
        return fmt.Errorf("failed to register containsSpecialChar validator: %w", err)
    }

    // 注册phone并检查错误
    if err := validate.RegisterValidation("phone", func(fl validator.FieldLevel) bool {
        re := regexp.MustCompile(`^\+\d{1,3}-\d{9,10}$`)
        return re.MatchString(fl.Field().String())
    }); err != nil {
        return fmt.Errorf("failed to register phone validator: %w", err)
    }

    if user.Verified == nil {
        user.Verified = new(bool)
        *user.Verified = false
    }
    if user.Active == nil {
        user.Active = new(bool)
        *user.Active = true
    }

    if err := validate.Struct(user); err != nil {
        var errMessages []string
        for _, err := range err.(validator.ValidationErrors) {
            errMessages = append(errMessages, fmt.Sprintf("field %s failed validation: %s", err.Field(), err.Tag()))
        }
        return errors.New(strings.Join(errMessages, ", "))
    }
    return nil
}

内容的提问来源于stack exchange,提问作者John Mark

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 16:02:53