这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天
校验参数
web开发无法避免的参数校验,大量的参数校验很容易导致代码中有大量的if判断,在这里推荐一种比较方便的方法。
参数校验的意义
大多数方法对传递给它们的参数值有限制。例如,索引值必须非负,对象引用必须非空。
- 应该清楚地在文档中记录所有这些限制,并在方法主体的开头使用检查来实施它们。
- 应该在错误发生后尽快找到它们,这是一般原则。如果不这样做,就不太可能检测到错误,而且即使检测到错误,确定错误的来源也很难。
若一个无效参数被传递给一个方法
- 若该方法在执行前检查参数,这过程将迅速失败,并引发异常
- 若方法未检查参数,可能会在处理过程中出现:
-
- 莫名其妙的异常而失败
- 正常返回,但会悄悄计算错误结果
- 正常返回,但会使某对象处于隐患状态,可能在未来某不确定时间在某不相关代码点报错。
总之,若无验证参数,可能会违反失败原子性。
对public、protected方法,要在方法说明使用 Javadoc 的 @throws 标签说明如果违反参数值限制时会抛出的异常。通常为 IllegalArgumentException、IndexOutOfBoundsException 或 NullPointerException。一旦在文档中记录了方法参数上的限制,并且记录违反这些限制将引发的异常,强加这些限制就很简单了。
参数校验,为了保护自己的代码,一般都会在开发中假设所有的参数都是不可靠的。针对所有的参数校验场景自己一次进行判断及错误信息的提示。
配置
go get -u github.com/go-ozzo/ozzo-validation/v4
ozzo-validation 是一个 Go 包,提供可配置和可扩展的数据验证功能。它具有以下功能:
- 使用常规编程构造而不是容易出错的结构标记来指定应如何验证数据。
- 可以验证不同类型的数据,例如结构、字符串、字节切片、切片、映射、数组。
- 可以验证自定义数据类型,只要它们实现
Validatable接口。 - 可以验证实现 SQL 的数据类型
sql.Valuer接口(例如sql.NullString). - 可自定义且格式良好的验证错误。
- 错误代码和消息转换支持。
- 提供一组开箱即用的丰富验证规则。
- 非常容易创建和使用自定义验证规则。
参考教程
代码
middleware/validator/validator.go
注意此处定义了校验的结构体,并使用validation.ValidateStruct对结构体进行校验(这是此处的关键)
对于结构值,通常需要检查其字段是否有效。例如,在 RESTful 应用程序中,您可以将请求有效负载取消封送到结构中,然后验证结构字段。如果一个或多个字段无效,您可能希望收到描述哪些字段无效的错误。可以使用validation.ValidateStruct() 来实现这个目的。单个结构可以具有多个字段的规则,并且一个字段可以与多个规则相关联。
请注意,在调用validation.ValidateStruct验证结构 若要验证结构,应向该方法传递指向结构的指针,而不是结构本身。同样,在调用validation.Field字段 若要指定结构字段的规则,应使用指向结构字段的指针。
执行结构验证时,将按照字段在 ValidateStruct 中指定的顺序进行验证。验证每个字段时,也会按照其与字段关联的顺序评估其规则。如果规则失败,则会为该字段记录错误,并且验证将继续处理下一个字段。
package validator
import (
"github.com/gin-gonic/gin"
validation "github.com/go-ozzo/ozzo-validation"
"go_douyin/global/consts" "go_douyin/global/variable" "go_douyin/utils/response")
type Login struct {
Username string `json:"username"`
Password string `json:"password"`
}
type Register struct {
Username string `json:"username"`
Password string `json:"password"`
}
func LoginValidationMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
var login Login
if err := c.ShouldBindJSON(&login); err != nil {
variable.ZapLog.Info(err.Error())
response.Fail(c, consts.ValidatorParamsCheckFailCode, consts.ValidatorParamsCheckFailMsg, gin.H{})
c.Abort()
return
}
variable.ZapLog.Info(login.Username)
if err := validation.ValidateStruct(&login,
validation.Field(&login.Username, validation.Required),
validation.Field(&login.Password, validation.Required),
); err != nil {
variable.ZapLog.Info(err.Error())
response.Fail(c, consts.ValidatorParamsCheckFailCode, consts.ValidatorParamsCheckFailMsg, gin.H{})
c.Abort()
return
}
c.Set("login", login)
c.Next()
}
}
router/router.go
这里调用了参数校验的中间件
// 用户组:登录注册,获取个人信息
v1 := router.Group("/douyin/user")
{
v1.POST("login", validator.LoginValidationMiddleware(), userController.Login)
}
userController.go
func (h *UserController) Login(c *gin.Context) {
var login validator.Login
login = c.MustGet("login").(validator.Login)
isLogin, userDB, token := h.UserService.Login(login.Username, login.Password)
if isLogin {
response.Success(c, "登录成功", gin.H{
"user_id": userDB.UserID,
"token": token,
})
} else {
response.Fail(c, -1, "登录失败", gin.H{})
}
}