跳转到内容

验证

Goravel 提供了几种不同的方法来验证应用程序的传入数据。 最常见的是使用所有传入 HTTP 请求上可用的 Validate 方法。 Goravel 包含各种方便的验证 规则。

验证快速入门

让我们仔细看一个完整的示例,了解如何验证表单并向用户返回错误消息。 这个 概述将为您提供使用 Goravel 验证传入请求数据的一般理解。

定义路由

首先,让我们假设我们在 routes/web.go 文件中定义了以下路由:

go
import "goravel/app/http/controllers"

postController := controllers.NewPostController()
facades.Route().Get("/post/create", postController.Create)
facades.Route().Post("/post", postController.Store)

GET 路由显示用于创建新博客文章的表单。 使用 POST 路由将新帖子存储到数据库中。

创建控制器

接下来,让我们看一个简单的控制器,它处理这些路由的传入请求。 我们暂时将 Store 方法保留为空:

go
package controllers

import (
  "github.com/goravel/framework/contracts/http"
)

type PostController struct {
  // 依赖服务
}

func NewPostController() *PostController {
  return &PostController{
    // 注入服务
  }
}

func (r *PostController) Create(ctx http.Context) {

}

func (r *PostController) Store(ctx http.Context) {

}

编写验证逻辑

现在我们准备在 Store 方法中填写验证新博客文章的逻辑。

go
func (r *PostController) Store(ctx http.Context) {
  validator, err := ctx.Request().Validate(map[string]string{
    "title": "required|max_len:255",
    "body": "required",
    "code": "required|regex:^\d{4,6}$",
  })
}

嵌套属性

如果传入的 HTTP 请求包含"嵌套"字段数据,您可以在验证规则中使用"点"语法来指定这些字段:

go
validator, err := ctx.Request().Validate(map[string]string{
  "title": "required|max_len:255",
  "author.name": "required",
  "author.description": "required",
})

切片验证

如果传入的 HTTP 请求包含"数组"字段数据,您可以在验证规则中使用 * 语法来指定这些字段:

go
validator, err := ctx.Request().Validate(map[string]string{
  "tags.*": "required",
})

表单请求验证

创建表单请求

对于更复杂的验证场景,您可能希望创建一个"表单请求"。 表单请求是封装了自己的验证和授权逻辑的自定义请求类。 要创建表单请求类,您可以使用 make:request Artisan CLI 命令:

go
go run . artisan make:request StorePostRequest
go run . artisan make:request user/StorePostRequest

生成的表单请求类将被放置在 app/http/requests 目录中。 如果该目录不存在, 在运行 make:request 命令时将会创建它。 Goravel 生成的每个表单请求都有六个方法: AuthorizeRules。 此外,你可以自定义 FiltersMessagesAttributesPrepareForValidation 方法来进行进一步的操作。

Authorize 方法负责确定当前认证用户是否可以执行请求所代表的操作, 而 Rules 方法返回应该应用于请求数据的验证规则:

go
package requests

import (
  "github.com/goravel/framework/contracts/http"
  "github.com/goravel/framework/contracts/validation"
)

type StorePostRequest struct {
  Name string `form:"name" json:"name"`
}

func (r *StorePostRequest) Authorize(ctx http.Context) error {
  return nil
}

func (r *StorePostRequest) Rules(ctx http.Context) map[string]string {
  return map[string]string{
    // 键与传入的键一致。
    "name": "required|max_len:255",
  }
}

func (r *StorePostRequest) Filters(ctx http.Context) map[string]string {
  return map[string]string{
    "name": "trim",
  }
}

func (r *StorePostRequest) Messages(ctx http.Context) map[string]string {
  return map[string]string{}
}

func (r *StorePostRequest) Attributes(ctx http.Context) map[string]string {
  return map[string]string{}
}

func (r *StorePostRequest) PrepareForValidation(ctx http.Context, data validation.Data) error {
  return nil
}

那么,验证规则是如何被评估的呢? 你只需要在控制器方法中类型提示请求即可。 传入的表单请求在控制器方法被调用之前就会被验证,这意味着你不需要在控制器中添加任何验证逻辑:

然后你可以在控制器中使用 ValidateRequest 方法来验证请求:

go
func (r *PostController) Store(ctx http.Context) {
  var storePost requests.StorePostRequest
  errors, err := ctx.Request().ValidateRequest(&storePost)
}

查看可用验证规则部分了解更多规则。

请注意,由于form传递的值默认为string类型,请求中的所有字段也应该是string类型,否则请使用JSON传递值。

授权表单请求

表单请求类还包含一个Authorize方法。 在此方法中,你可以确定已认证的用户是否实际有权更新给定资源。 例如,你可以确定用户是否实际拥有他们尝试更新的博客评论。 在这个方法中,你很可能会与你的授权门和策略进行交互:

go
func (r *StorePostRequest) Authorize(ctx http.Context) error {
  var comment models.Comment
  facades.Orm().Query().First(&comment)
  if comment.ID == 0 {
    return errors.New("未找到评论")
  }

  if !facades.Gate().Allows("update", map[string]any{
    "comment": comment,
  }) {
    return errors.New("无法更新评论")
  }

  return nil
}

error 将被传递给 ctx.Request().ValidateRequest 的返回值。

过滤输入数据

你可以通过改进表单请求的 Filters 方法来格式化输入数据。 此方法应返回一个 属性/过滤器的映射:

go
func (r *StorePostRequest) Filters(ctx http.Context) map[string]string {
  return map[string]string{
    "name": "trim",
  }
}

自定义错误消息

你可以通过重写 Messages 方法来自定义表单请求使用的错误消息。 此方法应 返回一个属性/规则对及其对应错误消息的数组:

go
func (r *StorePostRequest) Messages() map[string]string {
  return map[string]string{
    "title.required": "标题是必需的",
    "body.required": "消息是必需的",
  }
}

自定义验证属性

Goravel 的许多内置验证规则错误消息包含一个 :attribute 占位符。 如果您希望验证消息中的 :attribute 占位符被替换为自定义属性名称,您可以通过重写 Attributes 方法来指定自定义名称。 此方法应返回一个属性/名称对的数组:

go
func (r *StorePostRequest) Attributes() map[string]string {
  return map[string]string{
    "email": "电子邮件地址",
  }
}

准备验证输入

如果您需要在应用验证规则之前准备或清理请求中的任何数据,您可以使用 PrepareForValidation 方法:

go
func (r *StorePostRequest) PrepareForValidation(ctx http.Context, data validation.Data) error {
  if name, exist := data.Get("name"); exist {
    return data.Set("name", name.(string)+"1")
  }
  return nil
}

手动创建验证器

如果你不想在请求上使用 Validate 方法,你可以使用 facades.Validator 手动创建一个验证器实例。 门面的 Make 方法生成一个新的验证器实例:

go
func (r *PostController) Store(ctx http.Context) http.Response {
  validator, _ := facades.Validation().Make(
    map[string]any{
      "name": "Goravel",
    },
    map[string]string{
      "title": "required|max_len:255",
      "body":  "required",
    })

  if validator.Fails() {
    // 返回失败
  }

  var user models.User
  err := validator.Bind(&user)
  ...
}

传递给 Make 方法的第一个参数是要验证的数据,可以是 map[string]anystruct。 第二个参数是要应用于数据的验证规则数组。

自定义错误消息

如果需要,您可以为验证器实例提供自定义错误消息,以替代 Goravel 提供的默认错误消息。 您可以将自定义消息作为第三个参数传递给 Make 方法(也适用于 ctx.Request().Validate()):

go
validator, err := facades.Validation().Make(input, rules, validation.Messages(map[string]string{
  "required": "The :attribute field is required.",
}))

为给定属性指定自定义消息

有时您可能希望仅为特定属性指定自定义错误消息。 您可以使用 "点" 表示法来实现。 首先指定属性名称,然后是规则(也适用于 ctx.Request().Validate()):

go
validator, err := facades.Validation().Make(input, rules, validation.Messages(map[string]string{
  "email.required": "We need to know your email address!",
}))

指定自定义属性值

Goravel 的许多内置错误消息包含一个 :attribute 占位符,该占位符会被替换为正在验证的字段或属性的名称。 要自定义用于替换特定字段的这些占位符的值,您可以将自定义属性数组作为第三个参数传递给 Make 方法(也适用于 ctx.Request().Validate()):

go
validator, err := facades.Validation().Make(input, rules, validation.Attributes(map[string]string{
  "email": "email address",
}))

验证前格式化数据

您可以在验证数据之前格式化数据以实现更灵活的数据验证,并且可以将格式化数据的方法作为第三个参数传递给 Make 方法(也适用于 ctx.Request().Validate()):

go
import (
  validationcontract "github.com/goravel/framework/contracts/validation"
  "github.com/goravel/framework/validation"
)

func (r *PostController) Store(ctx http.Context) http.Response {
  validator, err := facades.Validation().Make(input, rules,
    validation.PrepareForValidation(func(ctx http.Context, data validationcontract.Data) error {
      if name, exist := data.Get("name"); exist {
        return data.Set("name", name)
      }

      return nil
    }))

  ...
}

使用验证后的输入

在使用表单请求或手动创建的验证器实例验证传入的请求数据后,如果你仍然想将请求数据绑定到一个 struct,有两种方法可以做到这一点:

  1. 使用 Bind 方法,这将绑定所有传入的数据,包括未经验证的数据:
go
validator, err := ctx.Request().Validate(rules)
var user models.User
err := validator.Bind(&user)

validator, err := facades.Validation().Make(input, rules)
var user models.User
err := validator.Bind(&user)
  1. 当您使用 request 进行验证时,传入的数据会自动绑定到表单:
go
var storePost requests.StorePostRequest
errors, err := ctx.Request().ValidateRequest(&storePost)
fmt.Println(storePost.Name)

处理错误消息

获取字段的一个错误消息(随机)

go
validator, err := ctx.Request().Validate(rules)
validator, err := facades.Validation().Make(input, rules)

message := validator.Errors().One("email")

检索字段的所有错误消息

go
messages := validator.Errors().Get("email")

检索所有字段的所有错误消息

go
messages := validator.Errors().All()

确定字段是否存在错误消息

go
if validator.Errors().Has("email") {
  //
}

可用的验证规则

以下是所有可用验证规则及其功能的列表:

名称描述
required检查值是必需的,且不能是零值。 例如,字段类型为bool,传递的值为false,无法通过验证。
required_ifrequired_if:anotherfield,value,... 如果另一个字段等于任何值,则验证中的字段必须存在且不能为空。
required_unlessrequired_unless:anotherfield,value,... 该验证字段必须存在且不能为空,除非另一字段等于任何值。
required_withrequired_with:foo,bar,... 仅当其他指定字段存在时,被验证的字段必须存在且不能为空。
required_with_allrequired_with_all:foo,bar,... 只有在所有其他指定字段都存在时,正在验证的字段必须存在且不能为空。
required_withoutrequired_without:foo,bar,... 仅在未提供其他指定字段时,验证字段必须呈现且不能为空。
required_without_allrequired_without_all:foo,bar,... 仅当所有其他指定字段均不存在时,待验证字段必须存在且不能为空。
int检查值是否为 intX uintX 类型,并支持大小检查。 例子:int int:2 int:2,12。 注意:使用注意事项
uint检查值是 uint(uintX) 类型,value >= 0
bool检查值是否为布尔字符串(true: "1", "on", "yes", "true", false: "0", "off", "no", "false")。
string检查值是字符串类型,并支持大小检查。 例子:string string:2 string:2,12
float检查值为 float(floatX) 类型
slice检查值是否为切片类型([]intX []uintX []byte []string
inin:foo,bar,… 检查值是否在给定的枚举中
not_innot_in:foo,bar,… 检查值是否不在给定的枚举中
starts_withstarts_with:foo 检查输入字符串值是否以给定子字符串开头
ends_withends_with:foo 检查输入字符串值是否以给定的子字符串结尾
betweenbetween:min,max 检查值是否为数字且在给定范围内
maxmax:value 检查值是否小于或等于给定值(intX uintX floatX
minmin:value 检查值是否大于或等于给定值(intX uintX floatX
eqeq:value 检查输入值是否等于给定值
nene:value 检查输入值是否不等于给定值
ltlt:value 检查值是否小于给定值(intX uintX floatX
gtgt:value 检查值是否大于给定值(intX uintX floatX)
lenlen:value 检查值的长度是否等于给定的大小(string array slice map
min_lenmin_len:value 检查值的最小长度是否为给定大小(string array slice map
max_lenmax_len:value 检查值的最大长度是否为给定大小(string array slice map
email检查值是否为电子邮件地址字符串
array检查值是否为数组,切片类型
map检查值是否为集合类型
eq_fieldeq_field:field 检查字段值是否等于另一个字段的值
ne_fieldne_field:field 检查字段值是否不等于另一个字段的值
gt_fieldgt_field:field 检查字段值是否大于另一个字段的值
gte_fieldgte_field:field 检查字段值是否大于或等于另一个字段的值
lt_fieldlt_field:field 检查字段值是否小于另一个字段的值
lte_fieldlte_field:field 检查字段值是否小于或等于另一个字段的值
file验证它是否是一个上传的文件
image检查是否上传了图片文件并支持后缀检查
date检查字段值是否为日期字符串
gt_dategt_date:value 检查输入值是否大于给定的日期字符串
lt_datelt_date:value 检查输入值是否小于给定的日期字符串
gte_dategte_date:value 检查输入值是否大于或等于给定日期字符串
lte_datelte_date:value 检查输入值是否小于或等于给定的日期字符串
alpha验证值仅包含字母字符
alpha_num检查仅包含字母和数字
alpha_dash检查仅包含字母、数字、破折号( - )和下划线( _ )
json检查值是否为 JSON 字符串
number检查值是否为数字字符串 >= 0
full_url检查值是否为完整的 URL 字符串(必须以 http 或 https 开头)
ip检查值为 IP(v4 或 v6)字符串
ipv4检查值为 IPv4 字符串
ipv6检查值为 IPv6 字符串
regex检查值是否可以通过正则表达式验证

规则使用注意事项

int

当使用 ctx.Request().Validate(rules) 进行验证时,传入的 int 类型数据将被 json.Unmarshal 解析为 float64 类型,这将导致 int 规则验证失败。

解决方案

选项 1:添加 validation.PrepareForValidation,在验证数据之前格式化数据;

选项 2:使用 facades.Validation().Make() 进行规则验证;

自定义验证规则

Goravel 提供了多种有用的验证规则;但是,您可能希望指定一些自己的规则。 注册自定义验证规则的一种方法是使用规则对象。 要生成一个新的规则对象,你可以简单地使用 make:rule Artisan 命令。

例如,如果你想验证一个字符串是否为大写,你可以使用这个命令创建一个规则。 Goravel 会将新规则保存在 app/rules 目录中。 如果此目录不存在,当您运行 Artisan 命令创建规则时,Goravel 将创建它。

go
go run . artisan make:rule Uppercase
go run . artisan make:rule user/Uppercase

创建规则后,我们需要定义其行为。 规则对象有两个方法:PassesMessagePasses 方法接收所有数据,包括要验证的数据和验证参数。 它应该返回 truefalse,具体取决于属性值是否有效。 Message 方法应返回验证失败时应使用的验证错误消息。

go
package rules

import (
  "strings"

  "github.com/goravel/framework/contracts/validation"
)

type Uppercase struct {
}

// Signature 规则的名称。
func (receiver *Uppercase) Signature() string {
  return "uppercase"
}

// Passes 判断验证规则是否通过。
func (receiver *Uppercase) Passes(data validation.Data, val any, options ...any) bool {
  return strings.ToUpper(val.(string)) == val.(string)
}

// Message 获取验证错误消息。
func (receiver *Uppercase) Message() string {
  return "The :attribute must be uppercase."
}

然后你需要在 app/providers/validation_service_provider.go 文件中的 rules 方法中注册该规则,并且 该规则可以像其他规则一样使用:

go
package providers

import (
  "github.com/goravel/framework/contracts/validation"
  "github.com/goravel/framework/facades"

  "goravel/app/rules"
)

type ValidationServiceProvider struct {
}

func (receiver *ValidationServiceProvider) Register() {

}

func (receiver *ValidationServiceProvider) Boot() {
  if err := facades.Validation().AddRules(receiver.rules()); err != nil {
    facades.Log().Errorf("添加规则错误:%+v", err)
  }
}

func (receiver *ValidationServiceProvider) rules() []validation.Rule {
  return []validation.Rule{
    &rules.Uppercase{},
  }
}

可用的验证过滤器

名称描述
int/toInt将值(string/intX/floatX)转换为int类型v.FilterRule("id", "int")
uint/toUint将值(string/intX/floatX)转换为uint类型 v.FilterRule("id", "uint")
int64/toInt64将值(string/intX/floatX)转换为 int64 类型 v.FilterRule("id", "int64")
float/toFloat将值(string/intX/floatX)转换为 float 类型
bool/toBool将字符串值转换为布尔值。 (true: "1", "on", "yes", "true", false: "0", "off", "no", "false")
trim/trimSpace清理字符串两侧的空白字符
ltrim/trimLeft清理字符串左侧的空白字符
rtrim/trimRight清除字符串右侧的空白字符
int/integer将值(string/intX/floatX)转换为 int 类型 v.FilterRule("id", "int")
lower/lowercase将字符串转换为小写
upper/uppercase将字符串转换为大写
lcFirst/lowerFirst将字符串的第一个字符转换为小写
ucFirst/upperFirst将字符串的第一个字符转换为大写
ucWord/upperWord将每个单词的第一个字符转换为大写
camel/camelCase将字符串转换为驼峰命名风格
snake/snakeCase将字符串转换为蛇形命名风格
escapeJs/escapeJS转义 JS 字符串
escapeHtml/escapeHTML转义 HTML 字符串
str2ints/strToInts将字符串转换为 int 切片 []int
str2time/strToTime将日期字符串转换为 time.Time
str2arr/str2array/strToArray将字符串转换为字符串切片 []string

自定义过滤器

Goravel提供了多种有用的过滤器,但是,您可能希望指定一些自己的过滤器。 要生成一个新的规则对象,您可以简单地使用 make:filter Artisan 命令。 让我们使用这个命令生成一个将字符串转换为整数的规则。 此规则已内置于框架中,我们只是作为示例创建它。 Goravel 将把这个新过滤器保存在 app/filters 目录中。 如果此目录不存在,Goravel 将在您运行创建规则的 Artisan 命令时创建它:

go
go run . artisan make:filter ToInt
// or
go run . artisan make:filter user/ToInt

一个过滤器包含两个方法:Signature HandleSignature 方法设置过滤器的名称。 Handle 方法执行特定的过滤逻辑:

go
package filters

import (
  "strings"

  "github.com/spf13/cast"
  "github.com/goravel/framework/contracts/validation"
)

type ToInt struct {
}

// Signature 过滤器的签名。
func (receiver *ToInt) Signature() string {
  return "ToInt"
}

// Handle 定义要应用的过滤器函数。
func (receiver *ToInt) Handle() any {
  return func (val any) int {
    return cast.ToString(val)
  }
}

然后你需要在 app/providers/validation_service_provider.go 文件中将过滤器注册到 filters 方法中,这样就可以像其他过滤器一样使用它了:

go
package providers

import (
  "github.com/goravel/framework/contracts/validation"
  "github.com/goravel/framework/facades"

  "goravel/app/filters"
)

type ValidationServiceProvider struct {
}

func (receiver *ValidationServiceProvider) Register() {

}

func (receiver *ValidationServiceProvider) Boot() {
  if err := facades.Validation().AddFilters(receiver.filters()); err != nil {
    facades.Log().Errorf("add filters error: %+v", err)
  }
}

func (receiver *ValidationServiceProvider) filters() []validation.Filter {
  return []validation.Filter{
    &filters.ToInt{},
  }
}

基于 MIT 许可发布