文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Go高效率开发Web参数校验的方式有哪些

2023-07-04 15:57

关注

本篇内容介绍了“Go高效率开发Web参数校验的方式有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

web开发中,你肯定见到过各种各样的表单或接口数据校验:

本文主要讨论服务器端参数校验

确保用户以正确格式输入数据,提交的数据能使后端应用程序正常工作,同时在一切用户的输入都是不可信的前提下(比如xss跨域脚本攻击,sql注入),参数验证是不可或缺的一环,也是很繁琐效率不高的一环,在对接表单提交或者api接口数据提交,程序里充斥着大量重复验证逻辑和if else语句,本文分析参数校验的三种方式,找出最优解,从而提高参数验证程序代码的开发效率。

需求场景:

常见的网站登陆场景

业务需求

接口一:场景:输入手机号,获取短信验证码校验需求:判断手机号非空,手机号格式是否正确接口二:场景:手机收到短信验证码,输入验证码,点击登陆校验需求:1、判断手机号非空,手机号格式是否正确;2、验证码非空,验证码格式是否正确

技术选型:web框架gin

第一种实现方式:自定义实现校验逻辑

package mainfunc main() {   engine := gin.New()    engine := gin.New()    ctrUser := controller.NewUser()    engine.POST("/user/login", ctrUser.Login)    ctrCaptcha := controller.NewCaptcha()    engine.POST("/captcha/send", ctrCaptcha.Send)    engine.Run()}--------------------------------------------------------------------------------package controllertype Captcha struct {}func (ctr *Captcha) Send(c *gin.Context) {   mobile := c.PostForm("mobile")   // 校验手机号逻辑   if mobile == "" {      c.JSON(http.StatusBadRequest, gin.H{"error": "手机号不能为空"})      return   }   matched, _ := regexp.MatchString(`^(1[3-9][0-9]\d{8})$`, mobile)   if !matched {      c.JSON(http.StatusBadRequest, gin.H{"error": "手机号格式不正确"})      return   }    c.JSON(http.StatusBadRequest, gin.H{"mobile": mobile})}type User struct {}func (ctr *User) Login(c *gin.Context) {   mobile := c.PostForm("mobile")   code := c.PostForm("code")   // 校验手机号逻辑   if mobile == "" {      c.JSON(http.StatusBadRequest, gin.H{"error": "手机号不能为空"})      return   }   matched, _ := regexp.MatchString(`^(1[3-9][0-9]\d{8})$`, mobile)   if !matched {      c.JSON(http.StatusBadRequest, gin.H{"error": "手机号格式不正确"})      return   }   // 校验手机号逻辑   if code == "" {      c.JSON(http.StatusBadRequest, gin.H{"error": "验证码不能为空"})      return   }   if len(code) != 4 {      c.JSON(http.StatusBadRequest, gin.H{"error": "验证码为4位"})      return   }   c.JSON(http.StatusBadRequest, gin.H{"mobile": mobile, "code": code})}

源码链接

代码分析:
参数验证函数放在Controller层;
这是一种比较初级也是最朴素的实现方式,在现实代码review中经常遇到,这样实现会有什么问题?
1、手机号码验证逻辑重复;
2、违背了controller层的职责,controller层充斥着大量的验证函数(Controller层职责:从HTTP请求中获得信息,提取参数,并分发给不同的处理服务);

重复代码是软件质量下降的重大来源!!!

重复代码会造成维护成本的成倍增加;
2、需求的变动导致需要修改重复代码,如果遗漏某处重复的逻辑,就会产生bug(例如手机号码增加12开头的验证规则);
3、重复代码会导致项目代码体积变得臃肿;

聪明的开发者肯定第一时间想到一个解决办法:提取出验证逻辑,工具包util实现IsMobile函数

package utilfunc IsMobile(mobile string) bool {   matched, _ := regexp.MatchString(`^(1[3-9][0-9]\d{8})$`, mobile)   return matched}代码分析:问题:代码会大量出现util.IsMobile、util.IsEmail等校验代码

思考:从面向对象的思想出发,IsMobile属于util的动作或行为吗?

第二种实现方式:模型绑定校验

技术选型:web框架gin自带的模型验证器中文提示不是很好用,这里使用govalidator 模型绑定校验是目前参数校验最主流的验证方式,每个编程语言的web框架基本都支持这种模式,模型绑定时将Http请求中的数据映射到模型对应的参数,参数可以是简单类型,如整形,字符串等,也可以是复杂类型,如Json,Json数组,对各种数据类型进行验证,然后抛出相应的错误信息。

源码链接

package requestfunc init() {   validator.TagMap["IsMobile"] = func(value string) bool {      return IsMobile(value)   }}func IsMobile(value string) bool {    matched, _ := regexp.MatchString(`^(1[1-9][0-9]\d{8})$`, value)    return matched}type Captcha struct {   Mobile string `form:"mobile" valid:"required~手机号不能为空,numeric~手机号码应该为数字型,IsMobile~手机号码格式错误"`}type User struct {   Mobile string `form:"mobile" valid:"required~手机号不能为空,numeric~手机号码应该为数字型,IsMobile~手机号码格式错误"`   Code string `form:"code" valid:"required~验证码不能为空,numeric~验证码应该为数字型"`}-------------------------------------------------------------------------------package controllertype Captcha struct {}func (ctr *Captcha) Send(c *gin.Context) {   request := new(request.Captcha)   if err := c.ShouldBind(request); err != nil {      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})      return   }   if _, err := validator.ValidateStruct(request); err != nil {      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})      return   }   c.JSON(http.StatusBadRequest, gin.H{"data": request})}type User struct {}func (ctr *User) Login(c *gin.Context) {   request := new(request.User)   if err := c.ShouldBind(request); err != nil {      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})      return   }   if _, err := validator.ValidateStruct(request); err != nil {      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})      return   }   c.JSON(http.StatusBadRequest, gin.H{"data": request})}

代码分析:
1、mobile校验逻辑同样重复(注释实现校验的逻辑重复,如错误提示"手机号不能为空"修改为"请填写手机号",需要修改两个地方)
2、validator.ValidateStruct函数会验证结构体所有属性

对于2问题不太好理解,举例解释业务场景:用户注册功能,需要校验手机号、短信验证码、密码、昵称、生日type User struct {   Mobile string `form:"mobile" valid:"required~手机号不能为空,numeric~手机号码应该为数字型,IsMobile~手机号码格式错误"`   Code string `form:"code" valid:"required~验证码不能为空,numeric~验证码应该为数字型"`   Password string `form:"password" valid:"required~密码不能为空,stringlength(6|18)~密码6-18个字符"`   Nickname string `form:"nickname" valid:"required~昵称不能为空,stringlength(2|10)~昵称2-10个字符"`   Birthday time.Time `form:"birthday" valid:"required~生日不能为空" time_format:"2006-01-02"`}

代码分析:
登陆功能需要校验Mobile、Code属性;
注册功能需要校验Mobile、Code、Password、Nickname、Birthday属性;

如果代码校验共用User结构体,就产生了一个矛盾点,有两种方法可以解决这一问题:

// 只做Mobile、Code属性校验或者忽略Mobile、Code属性校验validator.ValidateStruct(user, "Mobile", "Code") 这种也是一种不错的解决方式,但是在项目实践中会遇到点小问题:1、一个校验结构体有20个属性,只需要校验其中10个字段,不管用白名单还是黑名单都需要传10个字段;2、手写字段名容易出错;
type UserLogin struct {   Mobile string `form:"mobile" valid:"required~手机号不能为空,numeric~手机号码应该为数字型,IsMobile~手机号码格式错误"`   Code string `form:"code" valid:"required~验证码不能为空,numeric~验证码应该为数字型"`}type UserRegister struct {   Mobile string `form:"mobile" valid:"required~手机号不能为空,numeric~手机号码应该为数字型,IsMobile~手机号码格式错误"`   Code string `form:"code" valid:"required~验证码不能为空,numeric~验证码应该为数字型"`   Password string `form:"password" valid:"required~密码不能为空,stringlength(6|18)~密码6-18个字符"`   Nickname string `form:"nickname" valid:"required~昵称不能为空,stringlength(2|10)~昵称2-10个字符"`   Birthday time.Time `form:"birthday" valid:"required~生日不能为空" time_format:"2006-01-02"`}代码解析:用户登陆接口对应:UserLogin结构体用户注册接口对应:UserRegister结构体

同样问题再次出现,Mobile、Code属性校验逻辑重复。

再介绍第三种参数校验方式之前,先审视一下刚才的一段代码:

if err := c.ShouldBind(&request); err != nil {  c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})  return}if _, err := validator.ValidateStruct(request); err != nil {  c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})  return}

参数绑定校验的地方都需要出现这几行代码,我们可以修改gin源码,把govalidator库集成在gin中;
如何修改第三方库源代码参照项目 源码链接

在gin根目录增加context_validator.go文件,代码如下:package ginimport (   "github.com/asaskevich/govalidator")type Validator interface {   Validate() error}func (c *Context) ShouldB(data interface{}) error {   if err := c.ShouldBind(data); err != nil {      return err   }   if _, err := govalidator.ValidateStruct(data); err != nil {      return err   }   var v Validator   var ok bool   if v, ok = data.(Validator); !ok {      return nil   }   return v.Validate()}

controller层的参数绑定校验代码如下:

type User struct {}func (ctr *User) Register(c *gin.Context) {   request := new(request.UserRegister)   if err := c.ShouldB(request); err != nil {      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})      return   }   c.JSON(http.StatusBadRequest, gin.H{"data": request})}

代码分析:
增加了Validator接口,校验模型实现Validator接口,可以完成更为复杂的多参数联合校验检查逻辑,如检查密码和重复密码是否相等

type UserRegister struct {   Mobile string `form:"mobile" valid:"required~手机号不能为空,numeric~手机号码应该为数字型,IsMobile~手机号码格式错误"`   Code string `form:"code" valid:"required~验证码不能为空,numeric~验证码应该为数字型"`   Password string `form:"password" valid:"required~密码不能为空,stringlength(6|18)~密码6-18个字符"`   RePassword string `form:"rePassword" valid:"required~重复密码不能为空,stringlength(6|18)~重复密码6-18个字符"`   Nickname string `form:"nickname" valid:"required~昵称不能为空,stringlength(2|10)~昵称2-10个字符"`   Birthday time.Time `form:"birthday" valid:"required~生日不能为空" time_format:"2006-01-02"`}func (req *UserRegister) Validate() error {   if req.Password != req.RePassword {      return errors.New("两次密码不一致")   }   return nil}

模型校验是通过反射机制来实现,众所周知反射的效率都不高,现在gin框架集成govalidator,gin原有的校验功能就显得多余,小伙伴们可以从ShouldBind函数从下追,把自带的校验功能屏蔽,提高框架效率。

第三种实现方式:拆解模型字段,组合结构体

解决字段校验逻辑重复的最终方法就是拆解字段为独立结构体,通过多个字段结构体的不同组合为所需的校验结构体,代码如下:
源码链接

package captchatype CodeS struct {   Code string `form:"code" valid:"required~验证码不能为空,numeric~验证码应该为数字型"`}package usertype PasswordS struct {   Password string `form:"password" valid:"required~密码不能为空,stringlength(6|18)~密码6-18个字符"`}type RePasswordS struct {   RePassword string `form:"rePassword" valid:"required~重复密码不能为空,stringlength(6|18)~重复密码6-18个字符"`}type NicknameS struct {   Nickname string `form:"nickname" valid:"required~昵称不能为空,stringlength(2|10)~昵称2-10个字符"`}type BirthdayS struct {   Birthday time.Time `form:"birthday" valid:"required~生日不能为空" time_format:"2006-01-02"`}type UserLogin struct {   MobileS   captcha.CodeS}type UserRegister struct {   MobileS   captcha.CodeS   user.PasswordS   user.RePasswordS   user.NicknameS   user.BirthdayS}func (req *UserRegister) Validate() error {   if req.Password() != req.RePassword() {      return errors.New("两次密码不一致")   }   return nil}代码解析:为什么字段结构体都加了S?1、结构体包含匿名结构体不能调用匿名结构体同名属性,匿名结构体加S标识为结构体示例代码不能很好的展示项目结构,可以查看源代码

代码分析:

接口user/login:    对应请求结构体UserLogin接口user/register: 对应请求结构体UserRegister接口captcha/send:  对应请求结构体CaptchaSend

总结:
一、验证逻辑封装在各自的实体中,由request层实体负责验证逻辑,验证逻辑不会散落在项目代码的各个地方,当验证逻辑改变时,找到对应的实体修改就可以了,这就是代码的高内聚;

二、通过不同实体的嵌套组合就可以实现多样的验证需求,使得代码的可重用性大大增强,这就是代码的低耦合

独立字段结构体组合成不同的校验结构体,这种方式在实际项目开发中有很大的灵活性,可以满足参数校验比较多变复杂的需求场景,小伙伴可以在项目开发中慢慢体会。

参数绑定校验在项目中遇到的几个问题

源码链接1、需要提交参数为json或json数组如何校验绑定?

type ColumnCreateArticle struct {   IDS   article.TitleS}type ColumnCreate struct {   column.TitleS   Article *ColumnCreateArticle `form:"article"`   Articles []ColumnCreateArticle `form:"articles"`}

严格遵循一个接口对应一个校验结构体

func (ctr *Column) Detail(c *gin.Context) {   request := new(request.IDS)   if err := c.ShouldB(request); err != nil {      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})      return   }   c.JSON(http.StatusBadRequest, gin.H{"data": request})}

示例代码获取文章专栏详情的接口,参数为专栏id,因为只有一个id参数,如果刚开始图省事,没有建立对应独立的ColumnDetail校验结构体,后期接口增加参数(例如来源等),还是要改动这一块代码,增加代码的不确定性

布尔参数的三种状态

type ColumnDetail struct {   IDS   // 为真显示重点文章,为否显示非重点文章,为nil都显示   ArticleIsImportant *bool `form:"articleIsImportant"`}column?id=1&articleIsImportant=true    ArticleIsImportant为truecolumn?id=1&articleIsImportant=false   ArticleIsImportant为falsecolumn?id=1                            ArticleIsIm

“Go高效率开发Web参数校验的方式有哪些”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯