v - 数据验证库
v 是一个功能强大、灵活的数据验证库,提供链式 API、丰富的内置验证规则、自定义错误消息和国际化支持。支持表单数据、结构体、API 参数等多种场景的验证。最低支持 Go 1.21.0 版本。
安装
go get -u go-slim.dev/v快速开始
package mainimport ( "fmt" "go-slim.dev/v")func main() { // 简单验证 err := v.Validate( v.Value("", "username", "用户名").Required(), v.Value("invalid-email", "email", "邮箱").IsEmail(), ) if err != nil { fmt.Println(err) // 输出: 用户名为必填字段 // 邮箱不是有效的电子邮箱地址 }}核心概念
验证器(Validator)
验证器是实现了 Validatable 接口的任何类型:
type Validatable interface { Validate() error}值验证器(Valuer)
Valuer 是核心的值验证器,用于验证单个值:
// Value(值, 字段名, 标签)validator := v.Value("alice@example.com", "email", "邮箱地址")- 值:要验证的实际数据
- 字段名:字段的标识符(如 “email”)
- 标签:用户可读的字段名称(如 “邮箱地址”)
基本使用
1. 单个值验证
import "go-slim.dev/v"// 必填验证err := v.Value("", "username", "用户名"). Required(). Validate()// 错误: 用户名为必填字段// 邮箱验证err := v.Value("alice@example.com", "email", "邮箱"). Required(). IsEmail(). Validate()// 通过// 链式验证err := v.Value("pass123", "password", "密码"). Required(). MinLength(8). MaxLength(32). Validate()// 错误: 密码最小长度为82. 多个值验证
Validate - 收集所有错误
执行所有验证器,收集所有错误:
err := v.Validate( v.Value("", "username", "用户名").Required(), v.Value("invalid", "email", "邮箱").IsEmail(), v.Value("123", "age", "年龄").Between(18, 100),)if err != nil { // err 包含所有验证错误 errs := err.(*v.Errors) // 获取第一个错误 first := errs.First() fmt.Println(first.String()) // 获取特定字段的错误 emailErrs := errs.Get("email") // 转换为 map errMap := errs.ToMap() // map[string][]*v.Error{ // "username": [...], // "email": [...], // }}Check - 快速失败
遇到第一个错误立即返回:
err := v.Check( v.Value("", "username", "用户名").Required(), v.Value("invalid", "email", "邮箱").IsEmail(), // 不会执行)// 只返回 username 的错误3. Map 数据验证
验证来自表单或 JSON 的 map 数据:
data := map[string]any{ "username": "alice", "email": "alice@example.com", "age": 25,}field := v.Map(data)err := v.Validate( field("username", "用户名").Required().MinLength(3), field("email", "邮箱").Required().IsEmail(), field("age", "年龄").Required().Between(18, 100),)4. 结构体验证
实现 Validatable 接口:
type User struct { Username string Email string Age int}func (u *User) Validate() error { return v.Validate( v.Value(u.Username, "username", "用户名"). Required(). MinLength(3). MaxLength(20), v.Value(u.Email, "email", "邮箱"). Required(). IsEmail(), v.Value(u.Age, "age", "年龄"). Required(). Between(18, 100), )}// 使用user := &User{Username: "alice", Email: "invalid", Age: 15}if err := user.Validate(); err != nil { // 处理验证错误}验证规则
必填验证
// Required - 值必须非空v.Value("", "name", "姓名").Required()// RequiredIf - 条件必填isAdmin := truev.Value("", "role", "角色").RequiredIf(isAdmin)// RequiredWith - 依赖其他值phone := ""email := "alice@example.com"v.Value(phone, "phone", "手机号").RequiredWith([]any{email})// 如果 email 不为空,则 phone 必填字符串验证
val := v.Value("hello", "text", "文本")// 长度验证val.Length(5) // 长度等于 5val.MinLength(3) // 最小长度 3val.MaxLength(10) // 最大长度 10val.LengthBetween(3, 10) // 长度在 3-10 之间// 内容验证val.Contains("ell") // 包含子串val.ContainsAny("aeiou") // 包含任意一个字符val.ContainsRune('h') // 包含特定字符val.Excludes("xyz") // 不包含子串val.ExcludesAll("123") // 不包含任何指定字符val.ExcludesRune('x') // 不包含特定字符val.StartsWith("he") // 以...开头val.StartsNotWith("x") // 不以...开头val.EndsWith("lo") // 以...结尾val.EndsNotWith("x") // 不以...结尾// 字符类型val.IsAlpha() // 只包含字母val.IsAlphanumeric() // 只包含字母和数字val.IsAlphaUnicode() // 只包含字母和 Unicode 字符val.IsAlphanumericUnicode() // 只包含字母数字和 Unicode 字符val.IsNumeric() // 数值字符串val.IsAscii() // ASCII 字符val.IsLower() // 小写val.IsUpper() // 大写// 格式验证val.IsHexadecimal() // 十六进制val.IsBase64() // Base64 编码val.IsBase64URL() // Base64 URL 编码val.IsURLEncoded() // URL 编码val.IsHTML() // HTML 内容val.IsHTMLEncoded() // HTML 转义数值验证
val := v.Value(42, "age", "年龄")// 比较val.Equal(42) // 等于val.NotEqual(0) // 不等于val.GreaterThan(18) // 大于val.GreaterEqualThan(18) // 大于等于val.LessThan(100) // 小于val.LessEqualThan(100) // 小于等于val.Between(18, 100) // 在范围内val.NotBetween(0, 17) // 不在范围内// 类型验证val.IsNumber() // 数字类型val.IsNumeric() // 数值val.IsBool() // 布尔值格式验证
// 网络相关v.Value("alice@example.com", "email", "邮箱").IsEmail()v.Value("https://example.com", "url", "网址").IsURL()v.Value("192.168.1.1", "ip", "IP地址").IsIP()v.Value("192.168.1.1", "ip", "IP地址").IsIPv4()v.Value("::1", "ip", "IP地址").IsIPv6()v.Value("00:11:22:33:44:55", "mac", "MAC地址").IsMAC()// 手机号v.Value("+8613800138000", "phone", "手机号").IsE164()v.Value("13800138000", "phone", "手机号").IsPhoneNumber()// UUIDv.Value("550e8400-e29b-41d4-a716-446655440000", "id", "ID").IsUUID()v.Value("...", "id", "ID").IsUUID3()v.Value("...", "id", "ID").IsUUID4()v.Value("...", "id", "ID").IsUUID5()v.Value("01ARZ3NDEKTSV4RRFFQ69G5FAV", "id", "ID").IsULID()// JWTv.Value("eyJhbGc...", "token", "令牌").IsJwt()// 哈希v.Value("5d41402abc4b2a76b9719d911017c592", "hash", "哈希").IsMD5()v.Value("...", "hash", "哈希").IsSHA256()v.Value("...", "hash", "哈希").IsSHA384()v.Value("...", "hash", "哈希").IsSHA512()// 颜色v.Value("#FF5733", "color", "颜色").IsHexColor()v.Value("rgb(255, 87, 51)", "color", "颜色").IsRgb()v.Value("rgba(255, 87, 51, 0.5)", "color", "颜色").IsRgba()v.Value("hsl(9, 100%, 60%)", "color", "颜色").IsHsl()v.Value("hsla(9, 100%, 60%, 0.5)", "color", "颜色").IsHsla()v.Value("#FF5733", "color", "颜色").IsColor() // 任意颜色格式// 坐标v.Value(39.9042, "lat", "纬度").IsLatitude()v.Value(116.4074, "lng", "经度").IsLongitude()// 时间v.Value("2024-01-15", "date", "日期").IsDatetime("2006-01-02")v.Value("Asia/Shanghai", "tz", "时区").IsTimezone()// 其他v.Value("1.2.3", "version", "版本").IsSemver()v.Value(`{"key":"value"}`, "data", "数据").IsJson()v.Value("/path/to/file", "path", "路径").IsFile()v.Value("/path/to/dir", "path", "路径").IsDir()枚举验证
// OneOf - 值必须在列表中status := "pending"v.Value(status, "status", "状态"). OneOf([]any{"pending", "active", "inactive"})// 实际应用role := "admin"v.Value(role, "role", "角色"). OneOf([]any{"admin", "user", "guest"})集合验证
Every - 所有元素验证
所有元素都必须通过验证:
numbers := []int{1, 2, 3, 4, 5}err := v.Value(numbers, "numbers", "数字列表"). Every(func(item *v.Item) any { // item.Index: 元素索引 // item.Value: 元素值 return v.Value(item.Value, "number", "数字"). Between(1, 10) }). Validate()// Map 验证data := map[string]int{ "a": 1, "b": 2, "c": 3,}err := v.Value(data, "data", "数据"). Every(func(item *v.Item) any { // item.Key: map 的键 // item.Value: map 的值 return v.Value(item.Value, "value", "值"). GreaterThan(0) }). Validate()Some - 任一元素验证
至少一个元素通过验证:
emails := []string{"invalid", "alice@example.com", "bad"}err := v.Value(emails, "emails", "邮箱列表"). Some(func(item *v.Item) any { return v.Value(item.Value, "email", "邮箱"). IsEmail() }). Validate()// 通过(至少有一个有效邮箱)条件验证
When - 条件执行
仅在条件满足时执行验证:
isAdmin := trueerr := v.Value("moderator", "role", "角色"). When(isAdmin, func(val *v.Valuer) { val.OneOf([]any{"admin", "superadmin"}) }). Validate()// 仅当 isAdmin 为 true 时验证角色Match - 模式匹配
根据值的不同执行不同的验证:
userType := "email"identifier := "alice@example.com"err := v.Value(identifier, "identifier", "标识符"). Match(func(m *v.Matcher) { m.Branch("email", func(val *v.Valuer) error { return val.IsEmail().Validate() }) m.Branch("phone", func(val *v.Valuer) error { return val.IsPhoneNumber().Validate() }) m.Fallback(func(val *v.Valuer) error { return v.NewError("invalid_type") }) }). Validate()// 实际应用:根据登录类型验证loginType := "phone"loginValue := "13800138000"err := v.Value(loginValue, "login", "登录凭证"). Match(func(m *v.Matcher) { // 使用邮箱登录 m.Branch("email", func(val *v.Valuer) error { return val.Required().IsEmail().Validate() }) // 使用手机号登录 m.Branch("phone", func(val *v.Valuer) error { return val.Required().IsPhoneNumber().Validate() }) // 使用用户名登录 m.Branch("username", func(val *v.Valuer) error { return val.Required().MinLength(3).Validate() }) // 默认情况 m.Fallback(func(val *v.Valuer) error { return v.NewError("unsupported_login_type") }) }). Validate()自定义验证
Custom - 自定义规则
// 返回 boolv.Value("hello", "text", "文本"). Custom("custom_check", func(val any) any { str := val.(string) return len(str) > 3 // true/false })// 返回 errorv.Value("hello", "text", "文本"). Custom("custom_check", func(val any) any { str := val.(string) if len(str) < 3 { return errors.New("太短了") } return nil // 或 true })// 返回 Validatablev.Value(user, "user", "用户"). Custom("check_user", func(val any) any { u := val.(*User) return u // 调用 u.Validate() })// 实际应用:检查用户名是否已存在v.Value("alice", "username", "用户名"). Custom("username_exists", func(val any) any { exists := db.UserExists(val.(string)) if exists { return errors.New("用户名已存在") } return true }, v.ErrorFormat("用户名 {value} 已被占用"))类型验证
import "reflect"// Typeof - 验证反射类型v.Value("hello", "text", "文本"). Typeof(reflect.String)v.Value(123, "num", "数字"). Typeof(reflect.Int)// IsString - 便捷的字符串类型验证v.Value("hello", "text", "文本").IsString()高级特性
1. 组合验证器
Every - 所有验证器都必须通过
validator := v.Every( v.Value("alice", "username", "用户名").Required(), v.Value("alice@example.com", "email", "邮箱").IsEmail(),)err := validator.Validate()// 所有验证都必须通过Some - 任一验证器通过即可
// 用户可以提供邮箱或手机号之一validator := v.Some( v.Value(email, "email", "邮箱").IsEmail(), v.Value(phone, "phone", "手机号").IsPhoneNumber(),)err := validator.Validate()// 至少一个通过即可2. IndexBy - 分组验证
用于多组参数,只要一组完整即可:
var index int// 用户可以提供:邮箱+密码 或 手机号+验证码err := v.IndexBy(&index, [][]any{ {email, password}, // 第 0 组 {phone, code}, // 第 1 组}).Validate()if err == nil { switch index { case 0: // 使用邮箱密码登录 case 1: // 使用手机号验证码登录 }}// 实际应用type LoginRequest struct { Email string Password string Phone string Code string}func (r *LoginRequest) Validate() error { var index int err := v.Validate( v.IndexBy(&index, [][]any{ {r.Email, r.Password}, {r.Phone, r.Code}, }), ) if err != nil { return err } // 根据 index 执行不同的验证 switch index { case 0: return v.Validate( v.Value(r.Email, "email", "邮箱").Required().IsEmail(), v.Value(r.Password, "password", "密码").Required().MinLength(6), ) case 1: return v.Validate( v.Value(r.Phone, "phone", "手机号").Required().IsPhoneNumber(), v.Value(r.Code, "code", "验证码").Required().Length(6), ) } return nil}3. 函数式验证
Wrap - 包装验证函数
// 创建可复用的验证器checkUsername := v.Wrap("alice", func(val any) error { username := val.(string) if db.UserExists(username) { return errors.New("用户名已存在") } return nil})err := checkUsername.Validate()Checker - 函数类型
// Checker 实现了 Validatable 接口var passwordCheck v.Checker = func() error { if password != confirmPassword { return errors.New("密码不匹配") } return nil}err := v.Validate( v.Value(password, "password", "密码").Required(), passwordCheck, // 可以直接使用)错误处理
Error 结构
type Error struct { // 错误代码(如 "required", "is_email") Code() string // 错误格式化模板 Format() string // 格式化参数 Params() map[string]any // 字段名 Field() string // 字段标签(用户可读) Label() string // 验证的值 Value() any // 内部错误 Internal() error}自定义错误消息
// 使用 ErrorFormatv.Value("", "username", "用户名"). Required(v.ErrorFormat("请输入{label}"))// 使用 ErrorParam 添加参数v.Value("ab", "username", "用户名"). MinLength(3, v.ErrorFormat("{label}至少需要 {min} 个字符,当前只有 {length} 个"), v.ErrorParam("length", 2), )// 使用 ErrorCode 自定义错误代码v.Value("", "username", "用户名"). Required(v.ErrorCode("custom_required"))Errors 集合
err := v.Validate( v.Value("", "username", "用户名").Required(), v.Value("invalid", "email", "邮箱").IsEmail(),)if err != nil { errs := err.(*v.Errors) // 检查是否为空 if !errs.IsEmpty() { // 获取第一个错误 first := errs.First() fmt.Println(first.String()) // 获取所有错误 all := errs.All() // 获取特定字段的错误 usernameErrs := errs.Get("username") // 转换为 map[字段名][]*Error errMap := errs.ToMap() for field, fieldErrs := range errMap { fmt.Printf("%s: %d 个错误\n", field, len(fieldErrs)) } // 字符串表示(所有错误) fmt.Println(errs.String()) }}国际化
设置默认翻译器
import "go-slim.dev/v"// 自定义翻译函数v.SetDefaultTranslator(func(message string, params map[string]any) string { // 从翻译文件或数据库加载翻译 translated := i18n.Translate(message, params) // 替换参数占位符 for key, value := range params { translated = strings.ReplaceAll( translated, "{"+key+"}", fmt.Sprintf("%v", value), ) } return translated})内置中文消息
v 包已内置中文错误消息:
// 这些错误代码已有中文翻译"required" -> "{label}为必填字段""is_email" -> "{label}不是有效的电子邮箱地址""is_phone_number" -> "{label}不是有效的手机号码""min_length" -> "{label}最小长度为{min}""between" -> "{label}必须大于或等于{min}且小于或等于{max}"// ... 等等使用场景
1. Web 表单验证
import ( "go-slim.dev/slim" "go-slim.dev/v")type RegisterForm struct { Username string `json:"username"` Email string `json:"email"` Password string `json:"password"` ConfirmPassword string `json:"confirm_password"` Age int `json:"age"` Terms bool `json:"terms"`}func (f *RegisterForm) Validate() error { return v.Validate( v.Value(f.Username, "username", "用户名"). Required(). MinLength(3). MaxLength(20). IsAlphanumeric(), v.Value(f.Email, "email", "邮箱"). Required(). IsEmail(), v.Value(f.Password, "password", "密码"). Required(). MinLength(8). MaxLength(32), v.Value(f.ConfirmPassword, "confirm_password", "确认密码"). Required(). Equal(f.Password, v.ErrorFormat("两次输入的密码不一致"), ), v.Value(f.Age, "age", "年龄"). Required(). Between(18, 100), v.Value(f.Terms, "terms", "服务条款"). Custom("must_accept", func(val any) any { return val.(bool) == true }, v.ErrorFormat("必须同意服务条款")), )}func registerHandler(c slim.Context) error { var form RegisterForm if err := c.Bind(&form); err != nil { return c.JSON(400, map[string]any{"error": "无效的请求数据"}) } if err := form.Validate(); err != nil { errs := err.(*v.Errors) return c.JSON(422, map[string]any{ "errors": errs.ToMap(), }) } // 创建用户... return c.JSON(200, map[string]any{"success": true})}2. API 参数验证
func updateUserHandler(c slim.Context) error { data := map[string]any{ "name": c.FormValue("name"), "email": c.FormValue("email"), "age": c.FormValue("age"), } field := v.Map(data) err := v.Validate( field("name", "姓名").Required().MinLength(2), field("email", "邮箱").Required().IsEmail(), field("age", "年龄").When(data["age"] != "", func(val *v.Valuer) { val.Between(18, 100) }), ) if err != nil { return c.JSON(422, map[string]any{"errors": err}) } // 更新用户... return c.JSON(200, map[string]any{"success": true})}3. 复杂业务验证
type Order struct { UserID int Items []OrderItem PaymentType string CardNumber string AlipayID string TotalAmount float64}type OrderItem struct { ProductID int Quantity int Price float64}func (o *Order) Validate() error { return v.Validate( // 用户 ID 必须存在 v.Value(o.UserID, "user_id", "用户ID"). Required(). Custom("user_exists", func(val any) any { return db.UserExists(val.(int)) }), // 订单项验证 v.Value(o.Items, "items", "订单项"). Required(). NotEmpty(). Every(func(item *v.Item) any { orderItem := item.Value.(OrderItem) return v.Validate( v.Value(orderItem.ProductID, "product_id", "商品ID"). Required(). Custom("product_exists", func(val any) any { return db.ProductExists(val.(int)) }), v.Value(orderItem.Quantity, "quantity", "数量"). Required(). Between(1, 100), v.Value(orderItem.Price, "price", "价格"). Required(). GreaterThan(0), ) }), // 支付方式验证 v.Value(o.PaymentType, "payment_type", "支付方式"). Required(). OneOf([]any{"card", "alipay", "wechat"}). Match(func(m *v.Matcher) { // 信用卡支付需要卡号 m.Branch("card", func(val *v.Valuer) error { return v.Value(o.CardNumber, "card_number", "卡号"). Required(). Length(16). Validate() }) // 支付宝支付需要支付宝账号 m.Branch("alipay", func(val *v.Valuer) error { return v.Value(o.AlipayID, "alipay_id", "支付宝账号"). Required(). Validate() }) }), // 总金额验证 v.Value(o.TotalAmount, "total_amount", "总金额"). Required(). Custom("amount_match", func(val any) any { calculated := 0.0 for _, item := range o.Items { calculated += item.Price * float64(item.Quantity) } // 允许 0.01 的误差 diff := calculated - o.TotalAmount return diff < 0.01 && diff > -0.01 }, v.ErrorFormat("总金额与订单项不匹配")), )}4. 多场景验证
type User struct { ID int Username string Email string Password string Role string}// 创建时的验证func (u *User) ValidateCreate() error { return v.Validate( v.Value(u.Username, "username", "用户名"). Required(). MinLength(3). Custom("username_unique", func(val any) any { return !db.UsernameExists(val.(string)) }), v.Value(u.Email, "email", "邮箱"). Required(). IsEmail(). Custom("email_unique", func(val any) any { return !db.EmailExists(val.(string)) }), v.Value(u.Password, "password", "密码"). Required(). MinLength(8), v.Value(u.Role, "role", "角色"). Required(). OneOf([]any{"user", "admin"}), )}// 更新时的验证func (u *User) ValidateUpdate() error { return v.Validate( v.Value(u.ID, "id", "用户ID"). Required(). Custom("user_exists", func(val any) any { return db.UserExists(val.(int)) }), v.Value(u.Username, "username", "用户名"). When(u.Username != "", func(val *v.Valuer) { val.MinLength(3) }), v.Value(u.Email, "email", "邮箱"). When(u.Email != "", func(val *v.Valuer) { val.IsEmail() }), // 更新时不需要密码 v.Value(u.Password, "password", "密码"). When(u.Password != "", func(val *v.Valuer) { val.MinLength(8) }), )}5. 嵌套结构验证
type Address struct { Street string City string ZipCode string}func (a *Address) Validate() error { return v.Validate( v.Value(a.Street, "street", "街道").Required(), v.Value(a.City, "city", "城市").Required(), v.Value(a.ZipCode, "zip_code", "邮编"). Required(). Length(6). IsNumeric(), )}type Profile struct { Name string Age int Address Address}func (p *Profile) Validate() error { return v.Validate( v.Value(p.Name, "name", "姓名").Required(), v.Value(p.Age, "age", "年龄").Between(18, 100), // 嵌套验证 &p.Address, // 实现了 Validatable 接口 )}API 参考
核心函数
// 验证执行func Validate(validations ...Validatable) errorfunc Check(validations ...Validatable) error// 值验证器func Value(value any, field, label string) *Valuer// Map 验证器func Map(data map[string]any) func(name, label string) *Valuer// 组合验证func Every(validators ...Validatable) Checkerfunc Some(validators ...Validatable) Checkerfunc IndexBy(index *int, values [][]any, options ...ErrorOption) Checker// 包装函数func Wrap(value any, check func(any) error) Checker// 错误创建func NewError(code string, options ...ErrorOption) *Error// 错误选项func ErrorFormat(format string) ErrorOptionfunc ErrorParam(key string, value any) ErrorOptionfunc ErrorCode(code string) ErrorOption// 国际化func SetDefaultTranslator(translator Translator)func DefaultTranslator() TranslatorValuer 方法
// 必填func (v *Valuer) Required(options ...ErrorOption) *Valuerfunc (v *Valuer) RequiredIf(condition bool, options ...ErrorOption) *Valuerfunc (v *Valuer) RequiredWith(values []any, options ...ErrorOption) *Valuer// 字符串func (v *Valuer) Length(n int, options ...ErrorOption) *Valuerfunc (v *Valuer) MinLength(min int, options ...ErrorOption) *Valuerfunc (v *Valuer) MaxLength(max int, options ...ErrorOption) *Valuerfunc (v *Valuer) LengthBetween(min, max int, options ...ErrorOption) *Valuerfunc (v *Valuer) Contains(substr string, options ...ErrorOption) *Valuerfunc (v *Valuer) StartsWith(prefix string, options ...ErrorOption) *Valuerfunc (v *Valuer) EndsWith(suffix string, options ...ErrorOption) *Valuer// 数值func (v *Valuer) Equal(another any, options ...ErrorOption) *Valuerfunc (v *Valuer) Between(min, max any, options ...ErrorOption) *Valuerfunc (v *Valuer) GreaterThan(min any, options ...ErrorOption) *Valuerfunc (v *Valuer) LessThan(max any, options ...ErrorOption) *Valuer// 格式func (v *Valuer) IsEmail(options ...ErrorOption) *Valuerfunc (v *Valuer) IsPhoneNumber(options ...ErrorOption) *Valuerfunc (v *Valuer) IsURL(options ...ErrorOption) *Valuerfunc (v *Valuer) IsIP(options ...ErrorOption) *Valuerfunc (v *Valuer) IsUUID(options ...ErrorOption) *Valuer// 集合func (v *Valuer) Every(handle func(item *Item) any, options ...ErrorOption) *Valuerfunc (v *Valuer) Some(handle func(item *Item) any, options ...ErrorOption) *Valuer// 条件func (v *Valuer) When(condition bool, then func(*Valuer)) *Valuerfunc (v *Valuer) Match(handle func(m *Matcher)) *Valuer// 自定义func (v *Valuer) Custom(code string, check func(val any) any, options ...ErrorOption) *Valuer// 执行func (v *Valuer) Validate() error注意事项
-
空值处理:
- 非
Required的字段,空值会跳过后续验证 - 使用
NotEmpty验证非空但不必填的情况
- 非
-
错误收集:
Validate收集所有错误Check遇到第一个错误立即返回- 选择合适的方式以平衡性能和用户体验
-
自定义验证返回值:
true或nil:验证通过false:验证失败,使用默认错误消息error:验证失败,使用自定义错误消息Validatable:调用其Validate()方法
-
性能考虑:
- 自定义验证(如数据库查询)可能较慢
- 考虑在业务层缓存验证结果
- 对于大量数据,使用
Every时注意性能
-
错误消息占位符:
{label}: 字段标签{value}: 字段值{field}: 字段名- 自定义参数:通过
ErrorParam添加
-
链式调用:
- 所有验证方法返回
*Valuer,支持链式调用 - 最后调用
Validate()执行验证 - 或作为参数传递给
v.Validate()
- 所有验证方法返回
-
类型安全:
- 自定义验证需要手动类型断言
- 注意处理类型断言的 panic
- 使用
Typeof预先验证类型
相关链接
- GitHub 仓库
- go-slim.dev/is - 底层验证函数库
- 验证库对比