数据验证 #
一、验证概述 #
1.1 为什么需要验证 #
数据验证确保用户输入的数据符合预期格式和规则,防止无效数据进入系统。
1.2 验证方式 #
| 方式 | 说明 |
|---|---|
| 手动验证 | 代码中手动检查 |
| 结构体标签 | 使用validator库 |
| 自定义验证器 | 编写验证函数 |
二、使用Validator库 #
2.1 安装 #
bash
go get github.com/go-playground/validator/v10
2.2 基本使用 #
go
package main
import (
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
)
type User struct {
Name string `validate:"required,min=2,max=50"`
Email string `validate:"required,email"`
Age int `validate:"required,min=0,max=150"`
Phone string `validate:"omitempty,e164"`
}
var validate = validator.New()
func main() {
app := fiber.New()
app.Post("/users", func(c *fiber.Ctx) error {
var user User
if err := c.BodyParser(&user); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Invalid request body",
})
}
// 验证
if err := validate.Struct(user); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Validation failed",
"fields": formatErrors(err),
})
}
return c.JSON(user)
})
app.Listen(":3000")
}
func formatErrors(err error) map[string]string {
errors := make(map[string]string)
for _, err := range err.(validator.ValidationErrors) {
field := err.Field()
tag := err.Tag()
switch tag {
case "required":
errors[field] = field + " is required"
case "email":
errors[field] = field + " must be a valid email"
case "min":
errors[field] = field + " must be at least " + err.Param()
case "max":
errors[field] = field + " must be at most " + err.Param()
default:
errors[field] = field + " validation failed"
}
}
return errors
}
三、常用验证规则 #
3.1 字符串验证 #
go
type StringValidation struct {
Required string `validate:"required"` // 必需
Min string `validate:"min=5"` // 最小长度
Max string `validate:"max=100"` // 最大长度
MinMax string `validate:"min=5,max=100"` // 长度范围
Email string `validate:"email"` // 邮箱格式
URL string `validate:"url"` // URL格式
Alpha string `validate:"alpha"` // 仅字母
Alphanum string `validate:"alphanum"` // 字母和数字
Numeric string `validate:"numeric"` // 数字
Lowercase string `validate:"lowercase"` // 小写
Uppercase string `validate:"uppercase"` // 大写
Contains string `validate:"contains=foo"` // 包含
Excludes string `validate:"excludes=bar"` // 不包含
StartsWith string `validate:"startswith=prefix"` // 以...开始
EndsWith string `validate:"endswith=suffix"` // 以...结束
Regex string `validate:"regex=^[a-z]+$"` // 正则匹配
}
3.2 数字验证 #
go
type NumberValidation struct {
Required int `validate:"required"` // 必需
Min int `validate:"min=0"` // 最小值
Max int `validate:"max=100"` // 最大值
MinMax int `validate:"min=0,max=100"` // 范围
Positive int `validate:"gt=0"` // 大于0
Negative int `validate:"lt=0"` // 小于0
Range int `validate:"gte=0,lte=100"` // 0-100
OneOf int `validate:"oneof=1 2 3"` // 枚举值
}
3.3 其他验证 #
go
type OtherValidation struct {
EqField string `validate:"eqfield=ConfirmPassword"` // 等于其他字段
NeField string `validate:"nefield=OldPassword"` // 不等于其他字段
GtField int `validate:"gtfield=MinAge"` // 大于其他字段
GteField int `validate:"gtefield=MinAge"` // 大于等于
LtField int `validate:"ltfield=MaxAge"` // 小于其他字段
LteField int `validate:"ltefield=MaxAge"` // 小于等于
Unique []string `validate:"unique"` // 唯一值
Dive []string `validate:"dive,min=3"` // 数组元素验证
OmitEmpty string `validate:"omitempty,email"` // 空值跳过
}
四、验证中间件 #
4.1 通用验证中间件 #
go
func ValidateBody(s interface{}) fiber.Handler {
validate := validator.New()
return func(c *fiber.Ctx) error {
// 创建结构体副本
value := reflect.New(reflect.TypeOf(s).Elem()).Interface()
if err := c.BodyParser(value); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Invalid request body",
})
}
if err := validate.Struct(value); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Validation failed",
"fields": formatErrors(err),
})
}
c.Locals("validated", value)
return c.Next()
}
}
// 使用
app.Post("/users", ValidateBody(&CreateUserRequest{}), createUser)
4.2 查询参数验证 #
go
func ValidateQuery(s interface{}) fiber.Handler {
validate := validator.New()
return func(c *fiber.Ctx) error {
value := reflect.New(reflect.TypeOf(s).Elem()).Interface()
if err := c.QueryParser(value); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Invalid query parameters",
})
}
if err := validate.Struct(value); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Validation failed",
"fields": formatErrors(err),
})
}
c.Locals("validated", value)
return c.Next()
}
}
五、自定义验证 #
5.1 注册自定义验证器 #
go
validate := validator.New()
// 注册自定义验证
validate.RegisterValidation("phone", func(fl validator.FieldLevel) bool {
phone := fl.Field().String()
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
return matched
})
// 使用
type User struct {
Phone string `validate:"required,phone"`
}
5.2 验证函数示例 #
go
// 验证中国手机号
validate.RegisterValidation("cn_phone", func(fl validator.FieldLevel) bool {
phone := fl.Field().String()
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
return matched
})
// 验证身份证号
validate.RegisterValidation("id_card", func(fl validator.FieldLevel) bool {
idCard := fl.Field().String()
matched, _ := regexp.MatchString(`^\d{17}[\dXx]$`, idCard)
return matched
})
// 验证密码强度
validate.RegisterValidation("strong_password", func(fl validator.FieldLevel) bool {
password := fl.Field().String()
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
hasNumber := regexp.MustCompile(`[0-9]`).MatchString(password)
hasSpecial := regexp.MustCompile(`[!@#$%^&*]`).MatchString(password)
return hasUpper && hasLower && hasNumber && hasSpecial
})
六、错误处理 #
6.1 格式化错误信息 #
go
func formatErrors(err error) map[string]string {
errors := make(map[string]string)
for _, err := range err.(validator.ValidationErrors) {
field := err.Field()
tag := err.Tag()
param := err.Param()
var message string
switch tag {
case "required":
message = fmt.Sprintf("%s is required", field)
case "email":
message = fmt.Sprintf("%s must be a valid email address", field)
case "min":
message = fmt.Sprintf("%s must be at least %s characters", field, param)
case "max":
message = fmt.Sprintf("%s must be at most %s characters", field, param)
case "gte":
message = fmt.Sprintf("%s must be greater than or equal to %s", field, param)
case "lte":
message = fmt.Sprintf("%s must be less than or equal to %s", field, param)
case "oneof":
message = fmt.Sprintf("%s must be one of: %s", field, param)
default:
message = fmt.Sprintf("%s validation failed on %s", field, tag)
}
errors[field] = message
}
return errors
}
6.2 中文错误信息 #
go
var errorMessages = map[string]string{
"required": "字段不能为空",
"email": "必须是有效的邮箱地址",
"min": "长度不能小于%s",
"max": "长度不能大于%s",
"gte": "必须大于或等于%s",
"lte": "必须小于或等于%s",
"gt": "必须大于%s",
"lt": "必须小于%s",
"oneof": "必须是以下值之一: %s",
"url": "必须是有效的URL",
"alpha": "只能包含字母",
"alphanum": "只能包含字母和数字",
"numeric": "必须是数字",
}
func formatErrorsCN(err error) map[string]string {
errors := make(map[string]string)
for _, err := range err.(validator.ValidationErrors) {
field := err.Field()
tag := err.Tag()
param := err.Param()
if msg, ok := errorMessages[tag]; ok {
if param != "" {
errors[field] = fmt.Sprintf(msg, param)
} else {
errors[field] = msg
}
} else {
errors[field] = fmt.Sprintf("%s validation failed", field)
}
}
return errors
}
七、完整示例 #
7.1 用户注册验证 #
go
package main
import (
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
)
type RegisterRequest struct {
Username string `json:"username" validate:"required,min=3,max=20,alphanum"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8,max=32,strong_password"`
ConfirmPassword string `json:"confirm_password" validate:"required,eqfield=Password"`
Phone string `json:"phone" validate:"required,cn_phone"`
Age int `json:"age" validate:"required,gte=18,lte=120"`
Terms bool `json:"terms" validate:"required,eq=true"`
}
var validate *validator.Validate
func init() {
validate = validator.New()
// 注册自定义验证
validate.RegisterValidation("cn_phone", validateCNPhone)
validate.RegisterValidation("strong_password", validateStrongPassword)
}
func main() {
app := fiber.New()
app.Post("/register", func(c *fiber.Ctx) error {
var req RegisterRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Invalid request body",
})
}
if err := validate.Struct(req); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Validation failed",
"fields": formatErrors(err),
})
}
// 注册逻辑...
return c.JSON(fiber.Map{
"message": "Registration successful",
})
})
app.Listen(":3000")
}
八、总结 #
8.1 验证规则汇总 #
| 规则 | 说明 |
|---|---|
| required | 必需字段 |
| 邮箱格式 | |
| min | 最小值/长度 |
| max | 最大值/长度 |
| gte | 大于等于 |
| lte | 小于等于 |
| oneof | 枚举值 |
| url | URL格式 |
| alpha | 仅字母 |
| alphanum | 字母数字 |
8.2 下一步 #
现在你已经掌握了数据验证,接下来让我们学习 自定义验证,编写更复杂的验证逻辑!
最后更新:2026-03-28