
案例:
如上图为常见的网站登陆场景,用户第一步输入手机号,点击获取短信验证码;第二步输入手机收到的短信验证码,点击登录按钮完成登录。
发送验证码接口:
判断手机号非空,手机号数字型,手机号格式是否正确
登录接口:
判断手机号非空,手机号数字型,手机号格式是否正确
判断验证码非空,验证码数字型,验证码位数是否正确
V1版本:
type SmsCaptchaV1 struct {
Mobile string `binding:"required,number,mobile"`
}
type UserLoginV1 struct {
Mobile string `binding:"required,number,mobile"`
Captcha string `binding:"required,number,len=4"`
}
我们使用了gin自带的参数校验框架实现了手机和验证码校验需求,这里有什么问题吗?
Mobile的校验规则重复违反了DRY原则,当需要修改Mobile校验规则的时候,需要修改多个地方,就这是代码坏味道中的霰弹式修改。
那如何改进呢,我们来到了V2版本:
type MobileFieldV2 struct {
Mobile string `binding:"required,number,mobile"`
}
type CaptchaFieldV2 struct {
Captcha string `binding:"required,number,len=4"`
}
type SmsCaptchaV2 struct {
MobileFieldV2
}
type UserLoginV2 struct {
MobileFieldV2
CaptchaFieldV2
}
我们通过把Mobile、Captcha抽取出来,独立成两个校验小对象,把校验规则包含在这个小对象中,然后通过组合的方式继承了小对象中的校验规则,从而达到了重用的目的。
开发中参数校验分为两种:
1、必须验证,如上面两个接口
2、调用方传了才验证,不传不验证,比如常见的查询接口
V2版本的required
标签导致所有包含校验小对象都必须验证参数,不能灵活支持上述两种校验情况,所以我们来到了最终版本:
type MobileField struct {
Mobile string `binding:"number,mobile"`
}
type CaptchaField struct {
Captcha string `binding:"number,len=4"`
}
type SmsCaptcha struct {
MobileField `binding:"required"`
}
type UserLogin struct {
MobileField `binding:"required"`
CaptchaField `binding:"required"`
}
我们把required
标签提取到SmsCaptcha、UserLogin结构体中,由使用方灵活设置校验条件。
1、binding:"required"
必须验证
2、binding:"omitempty"
调用方传了才验证,不传不验证
type UserMany struct {
MobileField `binding:"omitempty"`
user.AgeField `binding:"omitempty"`
user.LevelField `binding:"omitempty"`
user.NicknameField `binding:"omitempty"`
}
gin自带的校验框架默认required
,所以必须验证的情况可以省略required
标签。
为什么AgeField、LevelField、NicknameField放在user目录中?
比如Level字段,有可能出现在多个表中,商家表中的Level字段很有可能与用户表中的Level字段校验规则不一致,所以分目录区分。
这个版本可以很从容的解决日常开发中的校验需求,避免了霰弹式修改,提高了代码内聚性。
那还存在什么问题呢?
当服务器需要支持gRPC时,protobuf配置手机参数校验时又导致了校验规则重复:
message UserLogin {
string Mobile = 1 [(buf.validate.field).string.len = 11, (buf.validate.field).string.pattern = "^(1[3-9][0-9]\d{8})$"];
string Captcha = 2 [(buf.validate.field).string.len = 4, (buf.validate.field).string.pattern = "^[0-9]*$"];
}
寻根溯源,为什么会出现这种情况,参数校验逻辑应该放在哪里合适,请看下回分解。