起步
在 Iris - 向后端传参 一篇中讲述了后端如何接收前端传递过来的参数,这一篇则准备讲述后端如何校验这些参数。
Iris 官方使用案例 借助 validator 进行参数校验,因而此篇与 Iris 框架关系不大,但又是 Web 开发中较为重要一环。
环境准备
由于涉及到前端的参数传递,我们需要准备以下前端代码:
fetch("http://127.0.0.1:8080/form", {
body: JSON.stringify({
// 数据内容
}),
method: "POST",
})
.then(resp => resp.json())
.then(data => console.log(data))
这是一段向后端发起 POST 请求,数据格式为 json 的 js 代码。//数据内容 表示需要传递的数据,是后续实验中唯一需要修改部分。
代码可以在 chrome console 中运行,亦可写入 html 文件中,不再累述。
后端代码需要设置允许跨域访问,这里我打算用中间件解决。
app.Use(func(ctx iris.Context) {
ctx.Header("Access-Control-Allow-Origin", "*")
ctx.Next()
})
普通参数校验
首先我们需要想明白一个问题:为什么 Iris 要借助三方库?大多时候,这类问题不消细想答案就有两个:1. 代码复用 2. 代码模块化。如果不使用三方库、官方库,应该如何进行参数校验?下边举个例子。
例:要求学生名字 不低于 3 个字母,年龄 不低于 15 岁,性别要么男要么女。
// Student 学生
type Student struct {
Name string `json:"name"`
Age int8 `json:"age"`
Gender string `json:"gender"`
}
// ValidateStudent 校验 Student
func ValidateStudent(s Student) bool {
// validate name
if len(s.Name) < 3 {
return false
}
// validate age
if s.Age < 15 {
return false
}
// validate gender
if s.Gender != "girl" && s.Gender != "boy" {
return false
}
return true
}
func main() {
...
app.Post("/form", func(ctx iris.Context) {
var stu Student
ctx.ReadJSON(&stu)
if isInvalid := ValidateStudent(stu); !isInvalid {
ctx.JSON(iris.Map{"error": "参数无效"})
} else {
ctx.JSON(iris.Map{"msg": "参数有效"})
}
})
...
}
js 测试代码只需要修改数据部分:
body: JSON.stringify({
name: "zhong",
age: 15,
gender: "girl"
}),
显然,ValidateStudent 这一类参数检查的代码是比较累赘的存在,复用率、灵活性不高,你需要对 Student 写一个检查器,如果有 Teacher 结构体你又要写一个检查器。
当然你可以将 ValidateStudent 函数进行拆分,参数校验时使用方式如下:
if isInvalid := ValidateName(stu.Name); !isInvalid {
return false
} else {
return true
}
// 同上操作
ValidateAge(stu.Age) ...
// 同上操作
ValidateGender(stu.Gender) ...
这样一来通过组合的方式,可以最大化实现代码复用。貌似还不错。但缺点是,你已经在不知不觉地“造轮子”了,这“轮子”还很 low。
Validator 入门
还是以上面那个例子,用 Validator 如何实现呢?
你需要引入 validator 包:
import "gopkg.in/go-playground/validator.v9"
代码实现如下:
// Student 学生
type Student struct {
Name string `json:"name" validate:"gte=3"`
Age int8 `json:"age" validate:"gte=15"`
Gender string `json:"gender" validate:"eq=girl|eq=boy"`
}
func main() {
...
// 实例一个检查器对象
validate := validator.New()
app.Post("/form", func(ctx iris.Context) {
var stu Student
ctx.ReadJSON(&stu)
// 进行参数检查
if err := validate.Struct(stu); err != nil {
ctx.JSON(iris.Map{"error": err.Error()})
} else {
ctx.JSON(iris.Map{"msg": "参数有效"})
}
})
app.Run(iris.Addr(":8080"))
}
validator 使用方法也是基本的三步骤。第一步:实例化一个对象(validator.New()
);第二步:以 tag 方式对结构体中的字段做限制(validate:"gte=15"
);第三步:启动检查(validate.Struct()
)。
以上使用的 gte(大于等于)、eq(等于) 都是内嵌函数,| 表示“或”,是 validator 能够自动解析的。更多内嵌函数以及用法可参看 package validator。本系列是 Iris 框架,不宜扩展太远,但有机会会把这一部分补充完整。
自定义 Validator Tag
validator 提供的内嵌函数是不够用的,借助 RegisterValidation
可实现自定义 tag。
换句话说,validate:"eq=girl|eq=boy"
我们看起来很累赘,现在用标签 gender 替换掉。
type Student struct {
...
Gender string `json:"gender" validate:"gender"`
}
validate.RegisterValidation("gender", func(fl validator.FieldLevel) bool {
// 获取 Field 的值
gender := fl.Field().String()
if gender != "girl" && gender != "boy" {
return false
}
return true
})
RegisterValidation
方法的第一个参数是标签名,第二个是检查逻辑,也就是一个返回 bool 值的函数。fl.Field()
返回值为 reflect.Value 类型,这涉及到了 Go 语言的反射,不做细讲。
如果你想给标签传递参数,如同eq=girl
,可以仿照 eq 的实现方式:
// eq 源码
func isEq(fl FieldLevel) bool {
field := fl.Field()
param := fl.Param() // 获取参数
switch field.Kind() { // 根据值的类型做相应处理
...
}
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
}
事实上也不需要大动干戈地重写检查逻辑,RegisterAlias
方法允许你为复杂的标签起别名。上述代码可以改写为以下方式:
type Student struct {
...
Gender string `json:"gender" validate:"gender"`
}
func main() {
app := iris.Default()
validate := validator.New()
// 注册别名
validate.RegisterAlias("gender", "eq=girl|eq=boy")
...
}
自定义 Validation Function
Validator Tag 针对的是字段(Field),而 Validation Function 针对的是结构体;前者控制颗粒度更细,后者较粗。一小一大,面面俱到。
type Student struct {
...
Gender string `json:"gender"`
}
func StudentValidationFunc(sl validator.StructLevel) {
// stu 是 Student 类型
stu := sl.Current().Interface().(Student)
// 检查逻辑
if stu.Gender != "girl" && stu.Gender != "boy" {
// 不规范的类型以 ReportError 方法报错
sl.ReportError(stu.Gender, "Gender", "gender", "", "")
}
}
func main() {
app := iris.Default()
validate := validator.New()
// 注册结构体检查器
validate.RegisterStructValidation(StudentValidationFunc, Student{})
...
app.Run(iris.Addr(":8080"))
}
RegisterStructValidation
方法第一个参数是检查方法,第二个参数是结构体类型的值。sl.Current()
返回 reflect.Value 类型,相应使用也涉及到反射知识。
你会不会觉得这同我们最初自己实现的 ValidateStudent 没什么区别呀!但是你别忘记,当你需要做参数校验时,validator 能够提供一致的检查风格 validate.Struct,手动实现的则需要调用不同的函数。一旦你想要将它们整齐划一,你就陷入造轮子的陷阱里去。
还不快抢沙发