手把手带你从0到1封装Gin框架:08 validator数据校验&自定义验证规则
这篇我们加一下请求参数的校验
基本使用
Gin 框架本身自带数据校验的,这里我们直接使用
新建app/request/admin_request.go
文件:
package request
type SaveAdmin struct {
ID uint `form:"id" json:"id"`
Name string `form:"name" json:"name" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
Age int `form:"age" json:"age" binding:"required,min=1,max=100"`
Gender int `form:"gender" json:"gender" binding:"required,oneof=1 2"`
}
这里先定义一个接收请求参数的结构体,并给每个字段定义了校验规则
修改app/route/admin.go
文件:
package route
import (
"eve/app/api/admin"
"github.com/gin-gonic/gin"
)
func genAdminRouter(rg *gin.RouterGroup) {
rg.GET("/profile", admin.AdminApi.Profile)
rg.POST("/save", admin.AdminApi.Save)
}
新增路由/admin/save
再新增Save接口的方法,修改app/api/admin/admin_api.go
文件:
package admin
import (
"eve/app/request"
"eve/app/response"
"eve/app/service"
"github.com/gin-gonic/gin"
)
type adminApi struct{}
func (a *adminApi) Profile(c *gin.Context) {
admin := service.AdminService.Profile()
response.Success(c, admin)
}
// Save 新增/编辑管理员信息
func (a *adminApi) Save(c *gin.Context) {
var admin request.SaveAdmin
if err := c.ShouldBind(&admin); err != nil {
response.ValidateFailed(c, err.Error())
}
}
参数校验应该有一个统一的返回格式,那我们再新增一个ValidateFailed
方法,修改app/response/response.go
文件:
...
func ValidateFailed(c *gin.Context, msg string) {
c.JSON(http.StatusOK, Response{
1,
nil,
msg,
})
}
...
然后启动服务,发起请求:
➜ ~ curl --location --request POST 'http://127.0.0.1:8082/admin/save'
{
"error_code": 1,
"data": null,
"message": "Key: 'SaveAdmin.Name' Error:Field validation for 'Name' failed on the 'required' tag\nKey: 'SaveAdmin.Password' Error:Field validation for 'Password' failed on the 'required' tag\nKey: 'SaveAdmin.Age' Error:Field validation for 'Age' failed on the 'required' tag\nKey: 'SaveAdmin.Gender' Error:Field validation for 'Gender' failed on the 'required' tag"
}
错误信息拆解: Key: 'SaveAdmin.Name' Error:Field validation for 'Name' failed on the 'required' tag Key: 'SaveAdmin.Password' Error:Field validation for 'Password' failed on the 'required' tag Key: 'SaveAdmin.Age' Error:Field validation for 'Age' failed on the 'required' tag Key: 'SaveAdmin.Gender' Error:Field validation for 'Gender' failed on the 'required' tag
本次请求没有提交任何参数,所以有报错,但是错误信息很不友好,不适合直接给前端展示
commit-hash: 0c4ee74
错误信息自定义
在上边c.ShouldBind(&admin)
返回了一个error
,这个error
有好几种类型,数据校验失败返回的是validator.ValidationErrors
类型,需要根据不同的类型来获取相应的错误信息
首先修改app/request/validator.go
文件:
package request
import (
"errors"
"github.com/go-playground/validator/v10"
)
// ValidateErrorMessages 错误消息类型
type ValidateErrorMessages map[string]string
// Validator 验证规则
type Validator interface {
GetMessages() ValidateErrorMessages
}
// GetErrorMsg 获取错误信息的方法
func GetErrorMsg(request interface{}, err error) string {
// 定义 ValidationErrors 变量,可能包含错误数据
var validationErrors validator.ValidationErrors
// 如果request是Validator类型,并且err是validator.ValidationErrors类型
if _, isValidator := request.(Validator); isValidator && errors.As(err, &validationErrors) && len(validationErrors) > 0 {
// 获取第一个错误信息
errMsg := validationErrors[0]
if message, exist := request.(Validator).GetMessages()[errMsg.Field()+"."+errMsg.Tag()]; exist {
return message
} else {
// 如果没有自定义消息,返回错误成员本身的错误信息
return errMsg.Error()
}
}
// 其他类型的错误直接返回错误信息
return err.Error()
}
可以看到我们定义了一个统一的验证器类型,还定义了GetErrorMsg
方法来获取错误信息,这个方法只是针对上边不友好的validator.ValidationErrors
类型的错误进行了解读,其他类型的错误还是直接返回错误信息
修改app/request/admin_request.go
文件:
package request
type SaveAdmin struct {
ID uint `form:"id" json:"id"`
Name string `form:"name" json:"name" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
Age int `form:"age" json:"age" binding:"required,min=1,max=100"`
Gender int `form:"gender" json:"gender" binding:"required,oneof=1 2"`
}
func (SaveAdmin) GetMessages() ValidateErrorMessages {
return ValidateErrorMessages{
"Name.required": "用户名称不能为空",
"Password.required": "密码不能为空",
"Age.required": "年龄不能为空",
"Age.min": "年龄最小为1",
"Age.max": "年龄最大为100",
"Gender.required": "性别不能为空",
"Gender.oneof": "性别只能是男或者女",
}
}
上边的Validator
类型定义了GetMessages
方法,这里实现验证器的GetMessages
方法
然后在修改app/api/admin/admin_api.go
文件中Save
方法:
...
// Save 新增/编辑管理员信息
func (a *adminApi) Save(c *gin.Context) {
var admin request.SaveAdmin
if err := c.ShouldBind(&admin); err != nil {
fmt.Println(err)
response.ValidateFailed(c, request.GetErrorMsg(admin, err))
}
}
...
通过request.GetErrormsg
方法来分析验证失败返回的err
并返回错误信息
修改之后再重启项目,再发起请求:
➜ ~ curl --location --request POST 'http://127.0.0.1:8082/admin/save' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "fawef",
"password": "23",
"age": 2
}'
{"error_code":1,"data":null,"message":"性别不能为空"}
➜ ~ curl --location --request POST 'http://127.0.0.1:8082/admin/save' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "fawef",
"password": "23",
"age": 2,
"gender": 3
}'
{"error_code":1,"data":null,"message":"性别只能是男或者女"}
可以看到自定义的错误信息已经生效了
commit-hash: 9c01407
自定义验证规则
通过官方文档可以看到validator提供了丰富的验证规则,但是如果官方没有我们项目中需要的验证规则呢?比如 手机号验证、是否在数据库中存在验证 等,这就需要我们自己定义验证规则,正好validator 也支持我们自定义验证规则
现在我们新增internal/validator/validator.go
文件:
package validator
import (
"eve/internal/global"
"fmt"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
"regexp"
"strings"
)
func InitValidator() {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 自定义验证器注册
_ = v.RegisterValidation("mobile", validateMobile)
_ = v.RegisterValidation("exist", validateExist)
}
}
// 校验手机号
func validateMobile(fl validator.FieldLevel) bool {
mobile := fl.Field().String()
// 手机号为空则不验证,这里只验证有值的
if mobile == "" {
return true
}
ok, _ := regexp.MatchString(`^1[3-9]\d{9}$`, mobile)
return ok
}
// 验证是否在数据库表中存在
func validateExist(fl validator.FieldLevel) bool {
value := fmt.Sprint(fl.Field())
// 如果值为空则不验证
if value == "" {
return true
}
param := strings.Split(fl.Param(), " ")
sql := fmt.Sprintf(`SELECT 1 FROM %s WHERE %s = '%s' LIMIT 1`, param[0], param[1], value)
var count int64
if global.DB.Raw(sql).Count(&count); count > 0 {
return true
}
return false
}
在初始化文件中调用一下,修改internal/bootstrap/init.go
文件:
func init() {
...
// 注册验证器
validator.InitValidator()
}
然后我们再修改app/request/admin_request.go
文件:
package request
type SaveAdmin struct {
ID uint `form:"id" json:"id" binding:"exist=admin id"`
Name string `form:"name" json:"name" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
Age int `form:"age" json:"age" binding:"required,min=1,max=100"`
Gender int `form:"gender" json:"gender" binding:"required,oneof=1 2"`
Mobile string `form:"mobile" json:"mobile" binding:"required,mobile"`
}
func (SaveAdmin) GetMessages() ValidateErrorMessages {
return ValidateErrorMessages{
"ID.exist": "记录不存在",
"Name.required": "用户名称不能为空",
"Password.required": "密码不能为空",
"Age.required": "年龄不能为空",
"Age.min": "年龄最小为1",
"Age.max": "年龄最大为100",
"Gender.required": "性别不能为空",
"Gender.oneof": "性别只能是男或者女",
"Mobile.required": "手机号不能为空",
"Mobile.mobile": "手机号格式不正确",
}
}
可以看到对ID字段绑定了exist=admin id
验证规则,表示这个值应该在表admin
的id
字段中存在,还给Mobile字段绑定了mobile
验证规则,表示这个值是一个手机号,这两个规则在上边都已经注册好了
测试:
➜ ~ curl --location --request POST 'http://127.0.0.1:8082/admin/save' \
--header 'Content-Type: application/json' \
--data-raw '{
"id": 2,
"name": "nfwoe",
"password": "23",
"age": 2,
"gender": 2,
"mobile": "18800008888"
}'
{"error_code":1,"data":null,"message":"记录不存在"}
➜ ~ curl --location --request POST 'http://127.0.0.1:8082/admin/save' \
--header 'Content-Type: application/json' \
--data-raw '{
"id": 1,
"name": "nfwoe",
"password": "23",
"age": 2,
"gender": 2,
"mobile": "1234"
}'
{"error_code":1,"data":null,"message":"手机号格式不正确"}
可以看到,验证规则都可以正常使用,当然我们还可以根据自己的需要来注册更多的验证规则,比如start_date
不能在end_date
之后等~
总结
- 基本使用
- 错误消息自定义
- 验证规则自定义
commit-hash: d9a7f16
转载自:https://juejin.cn/post/7396235489831649332