跳到主要内容

v - 数据验证库

v 是一个功能强大、灵活的数据验证库,提供链式 API、丰富的内置验证规则、自定义错误消息和国际化支持。支持表单数据、结构体、API 参数等多种场景的验证。最低支持 Go 1.21.0 版本。

安装

go get -u go-slim.dev/v

快速开始

package main

import (
"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()
// 错误: 密码最小长度为8

2. 多个值验证

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 := true
v.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) // 长度等于 5
val.MinLength(3) // 最小长度 3
val.MaxLength(10) // 最大长度 10
val.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()

// UUID
v.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()

// JWT
v.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 := true

err := 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 - 自定义规则

// 返回 bool
v.Value("hello", "text", "文本").
Custom("custom_check", func(val any) any {
str := val.(string)
return len(str) > 3 // true/false
})

// 返回 error
v.Value("hello", "text", "文本").
Custom("custom_check", func(val any) any {
str := val.(string)
if len(str) < 3 {
return errors.New("太短了")
}
return nil // 或 true
})

// 返回 Validatable
v.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
}

自定义错误消息

// 使用 ErrorFormat
v.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) error
func 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) Checker
func Some(validators ...Validatable) Checker
func 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) ErrorOption
func ErrorParam(key string, value any) ErrorOption
func ErrorCode(code string) ErrorOption

// 国际化
func SetDefaultTranslator(translator Translator)
func DefaultTranslator() Translator

Valuer 方法

// 必填
func (v *Valuer) Required(options ...ErrorOption) *Valuer
func (v *Valuer) RequiredIf(condition bool, options ...ErrorOption) *Valuer
func (v *Valuer) RequiredWith(values []any, options ...ErrorOption) *Valuer

// 字符串
func (v *Valuer) Length(n int, options ...ErrorOption) *Valuer
func (v *Valuer) MinLength(min int, options ...ErrorOption) *Valuer
func (v *Valuer) MaxLength(max int, options ...ErrorOption) *Valuer
func (v *Valuer) LengthBetween(min, max int, options ...ErrorOption) *Valuer
func (v *Valuer) Contains(substr string, options ...ErrorOption) *Valuer
func (v *Valuer) StartsWith(prefix string, options ...ErrorOption) *Valuer
func (v *Valuer) EndsWith(suffix string, options ...ErrorOption) *Valuer

// 数值
func (v *Valuer) Equal(another any, options ...ErrorOption) *Valuer
func (v *Valuer) Between(min, max any, options ...ErrorOption) *Valuer
func (v *Valuer) GreaterThan(min any, options ...ErrorOption) *Valuer
func (v *Valuer) LessThan(max any, options ...ErrorOption) *Valuer

// 格式
func (v *Valuer) IsEmail(options ...ErrorOption) *Valuer
func (v *Valuer) IsPhoneNumber(options ...ErrorOption) *Valuer
func (v *Valuer) IsURL(options ...ErrorOption) *Valuer
func (v *Valuer) IsIP(options ...ErrorOption) *Valuer
func (v *Valuer) IsUUID(options ...ErrorOption) *Valuer

// 集合
func (v *Valuer) Every(handle func(item *Item) any, options ...ErrorOption) *Valuer
func (v *Valuer) Some(handle func(item *Item) any, options ...ErrorOption) *Valuer

// 条件
func (v *Valuer) When(condition bool, then func(*Valuer)) *Valuer
func (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

注意事项

  1. 空值处理

    • Required 的字段,空值会跳过后续验证
    • 使用 NotEmpty 验证非空但不必填的情况
  2. 错误收集

    • Validate 收集所有错误
    • Check 遇到第一个错误立即返回
    • 选择合适的方式以平衡性能和用户体验
  3. 自定义验证返回值

    • truenil:验证通过
    • false:验证失败,使用默认错误消息
    • error:验证失败,使用自定义错误消息
    • Validatable:调用其 Validate() 方法
  4. 性能考虑

    • 自定义验证(如数据库查询)可能较慢
    • 考虑在业务层缓存验证结果
    • 对于大量数据,使用 Every 时注意性能
  5. 错误消息占位符

    • {label}: 字段标签
    • {value}: 字段值
    • {field}: 字段名
    • 自定义参数:通过 ErrorParam 添加
  6. 链式调用

    • 所有验证方法返回 *Valuer,支持链式调用
    • 最后调用 Validate() 执行验证
    • 或作为参数传递给 v.Validate()
  7. 类型安全

    • 自定义验证需要手动类型断言
    • 注意处理类型断言的 panic
    • 使用 Typeof 预先验证类型

相关链接