Gin框架中处理请求参数的零值问题

作者&投稿:布波 (若有异议请与网页底部的电邮联系)
~

Gin框架处理前端请求的时候,使用ShouldBindXXX绑定参数/数据到结构体上是一种比较常用的取数据手段,但在一些情况下,可能会出现问题

例如,现在有一个/user/update接口,用于更新用户的年龄和昵称,即接收两个字段:age(int)、nick_name(string),并且这两个字段并不要求必须同时传递,可以两个都传,也可以只传其中一个,后端从请求中解析这两个参数,取到哪个字段就对哪个字段进行更新

typeUserstruct{Ageint`json:"age"`NickNamestring`json:"nick_name"`}

两个字段都传递那还好说,但如果只传其中一个字段,并且后端用ShouldBindXXX来绑定数据到结构体,就可能会出现问题了

funcHandlerUpdate(c*gin.Context){varuserUseriferr:=c.ShouldBind(&user);err!=nil{//...}}

如果前端只传了一个nick_name字段,没传age字段,那么user.Age的值就是零值,即0,ShouldBindXXX并不判断这个0到底是零值还是前端真的传了0

这个问题解决起来倒也简单,两个方法

一是将结构体内的字段改成指针类型

typeUserstruct{Age*int`json:"age"`NickName*string`json:"nick_name"`}

指针的零值nil,ShouldBindXXX之后,字段值为nil的自然就是没传值的

但将结构体所有的字段都定义为指针类型未免有些不符合习惯,并且操作指针也不方便,也更容易出错(例如空指针问题)

第二个是办法是借助map

ShouldBindXXX有问题的话那我大不了不用了,直接将参数(GET)/数据(POST)映射到map就行

但这样的话就会引出另外一个问题,ShouldBindXXX方法一个显著的好处是可以根据结构体里定义的tag规则来对字段进行校验,如果你直接读到map中就要自己实现字段校验逻辑了,字段少点还好,要是多了得写一大串的if...else或者是干脆要实现一个通用校验方法了,未免繁琐

所以想到用ShouldBindXXX来做校验,再借助map用于区分零值,即对请求传递的数据读了两次

以GET请求为例:

funcHandlerUpdate(c*gin.Context){varuserUser//用ShouldBind作校验iferr:=c.ShouldBind(&user);err!=nil{fmt.Printf("genGetMapShouldBinderror:%v
",err)return}//请求真正传递的参数映射到map中allMap:=map[string]interface{}{}urlvalues:=c.Request.URL.Query()fork,urls:=rangeurlvalues{fmt.Printf("
genGetMapk,urls,%v,%v
",k,urls)//重复值则取最后一个allMap[k]=urls[len(urls)-1]}}

截至目前,只是校验并获取到了请求的数据,下一步还要进行更新数据库的操作,这里以gorm为例

因为user只是用于校验请求的数据是否合法,无法判断零值,所以不能直接以user为基础操作数据库

//可能因零值问题导致出现不符合预期的结果db.Save(&user)

allMap可以分辨出请求到底携带了哪些参数/数据,但可能存在一些额外不需要的数据,例如当希望更新用户的age和nick_name属性的时候,操作的数据表是db_user,而这个数据表中除了age、nick_name两列外,还存在用于标识用户是否注销了的is_del列,那么按照如下更新方式也是会出问题的:

db.Model(&user).Updates(allMap)

如果allMap中存在is_del属性,那么也会更新数据表中的is_del字段,并不是预期的结果,所以需要将allMap中不需要的属性去掉,可以复制出一份只包含所需更新属性的map,也可以直接删除掉allMap上额外的属性只保留所需的,这里以前一种为例

allMap:=make(map[string]interface{})realMap:=make(map[string]interface{})ifv,ok:=allMap["age"];ok{realMap["age"]=v}ifv,ok:=allMap["nick_name"];ok{realMap["nick_name"]=v}db.Model(&user).Updates(realMap)

这里只有age、nick_name两个字段所以还好,但如果所需更新的字段最多在5个以上就要写最多5个条件语句了,未免繁琐,可以借助reflect处理,无论存在多少个需要更新的字段,代码量都是一样的

realMap:=make(map[string]interface{})typ:=reflect.TypeOf(user).Elem()fori:=0;i<typ.NumField();i++{tagName:=typ.Field(i).Tag.Get("json")ifv,isOK:=allMap[tagName];isOK{realMap[tagName]=v}}db.Model(&user).Updates(realMap)

完整代码:

//将请求的参数映射到m中,如果是GET,返回query参数组成的map;如果是POST,返回请求体里的数据////instance:指向具体结构体实例的指针,作用是获取结构体中每个字段名为`json`的tag,以映射mapfuncGenMapByStruct(c*gin.Context,instanceinterface{},m*map[string]interface{})error{ifc.ContentType()!=gin.MIMEJSON{returnerrors.New("content-typemustbe"+gin.MIMEJSON)}ifc.Request.Method!=http.MethodGet&&c.Request.Method!=http.MethodPost{returnerrors.New("methodmustbeGETorPOST")}allMap:=map[string]interface{}{}ifc.Request.Method==http.MethodGet{iferr:=genGetMap(c,instance,&allMap);err!=nil{returnerr}}else{iferr:=genPostMap(c,instance,&allMap);err!=nil{returnerr}}typ:=reflect.TypeOf(instance).Elem()fori:=0;i<typ.NumField();i++{tagName:=typ.Field(i).Tag.Get("json")ifv,isOK:=allMap[tagName];isOK{(*m)[tagName]=v}}returnnil}//从get请求中获取query,并将query处理成map映射到allMap中funcgenGetMap(c*gin.Context,instanceinterface{},allMap*map[string]interface{})error{iferr:=c.ShouldBind(instance);err!=nil{fmt.Printf("genGetMapShouldBinderror:%v
",err)returnerr}urlvalues:=c.Request.URL.Query()fork,urls:=rangeurlvalues{fmt.Printf("
genGetMapk,urls,%v,%v
",k,urls)//重复值则取最后一个(*allMap)[k]=urls[len(urls)-1]}returnnil}//从post请求中获取body,并将body反序列化到allMap中funcgenPostMap(c*gin.Context,instanceinterface{},allMap*map[string]interface{})error{//shouldBind会导致body无法再次读取,方便起见这里使用了ShouldBindBodyWithiferr:=c.ShouldBindBodyWith(instance,binding.JSON);err!=nil{fmt.Printf("genPostMapShouldBinderror:%v
",err)returnerr}body,_:=c.Get(gin.BodyBytesKey)varbodyByte[]bytevarokboolifbodyByte,ok=body.([]byte);!ok{returnerrors.New("bodyisinvalid")}iflen(bodyByte)==0{returnnil}iferr:=json.Unmarshal(bodyByte,allMap);err!=nil{returnerr}returnnil}

使用示例:

typeUserstruct{Ageint`json:"age"`NickNamestring`json:"nick_name"`}r.Any("/update",func(c*gin.Context){m:=map[string]interface{}{}varuserUseriferr:=GenMapByStruct(c,&s,&m);err!=nil{c.JSON(http.StatusOK,gin.H{"code":-1,"message":err.Error()})return}c.JSON(http.StatusOK,gin.H{"code":0,"data":&m})})

可以看到,因为存在更多的计算过程,所以处理请求零值的情况,会带来更高的资源消耗,所以应该尽可能避免这种情况的出现,相比于在后端额外处理,让客户端携带完整的所需参数才是更优解。




adobe indesign中,位置工具是干什么用的呢
【位置工具】:该工具用于调整或移动框架内的图像大小,使其位于文档中的新位置。

springmvc框架(springmvc)
1.拦截器(Inteceptor)使用 监听器、过滤器、拦截器的对比:拦截器的执行流程:多个拦截器的执行流程:自定义SpringMVC拦截器:2.处理multipart形式的数据 文件上传:3.在控制器中处理异常:4.基于Flash属性的跨重定向请求数据传递:三、手写SpringMVC框架:四、SpringMVC源码剖析 1.前端控制器DispatcherServlet...

indesign如何处理图片?indesign怎么处理图片?
在InDesign中,您可以将栅格图像(例如用手机拍摄的图像)或矢量图形(例如徽标)带入文档中:1.选择“文件”>“放置”以在您的文档中放置一个图形(或多个图形)。2.单击打开。如果在放置之前选择了框架,则图形将放置在框架内。您还可以单击以将图形放置到现有的空框架中,或在放置图形时创建框架。3...

Indesign里的形状框架工具和形状有什么不同?
框架是用来装东西的:图片、文字、图形 图形工具就是图形咯,方、圆、黑、白

indesign 矩形框架工具和矩形工具的区别
图形框架工具的框是虚构的, 只用来起区域限制作用的 里面既可以置入图片也可以置入文字,实际不存在。图形工具具有图形框架工具的功能,不同的是图形工具可以设置边框颜色和填充颜色,实际存在的 有时候排版的时候并不想要有边框和底色,这时候用图形框架工具就方便很多 ...

indesign预览快捷键(indesign框架适合内容快捷键)
indesign使框架适合内容快捷键 在indesign软件中,要将页面进行放大显示的快捷键方式是:按下快捷键ctrl+ +(加号键)可以快速将页面放大一倍;另一种方式是按住空格键会自动切换成放大镜工具,这个时侯,按住鼠标左键在页面上拖动一段距离之后,可以将页面上拖动范围内的内容进行局部放大。indesign文本框...

indesign 无法显示框架网格
是不是因为视图太小的原因

如何理解 Tornado
让我们假设你正在写一个需要请求一些来自其他服务器上的数据(比如数据库服务,再比如新浪微博的open api)的应用程序,然后呢这些请求将花费一个比较长的时间,假设需要花费5秒钟。大多数的web开发框架中处理请求的代码大概长这样:def handler_request(self, request):answ = self.remote_server.query(...

在(ID) indesign中 处理图片时候 图片中心出现透明圆圈是什么 让他保...
这是CS5版本开始有的功能,只要用光标放在图中间,他就会有的,用来选择框架内的图像的。这个是自动有的。我一般都隐藏他,如果需要选择框架内的图像,你可以用白箭头或是黑箭头双击他,都可以选择框架内的图。他的隐藏与显示的命令在视图菜单里 ID大课堂2456870222。

电脑开机出现窗口:Incorrect close tag in element frame,什么问题?怎 ...
在打开网镖,切到“应用规则”一栏,你会发现你的那个错误框的标题在里边,点击,会看到它的路径。再用金山清理专家中“某某百宝箱”的粉碎功能把它所在的文件夹粉了,OK!(如有超级兔子,优化大师,完美卸载等工具可分析该exe卸载,这样比较好)在我机子上就这样干掉的...分啊...give me吧!

云县19185481485: java修改请求参数的值 -
点刚摄护: java.lang.IllegalStateException: No modifications are allowed to a locked ParameterMap.网上查了一些资料,解决方案是使用Wrapper,重写HttpServletRequestWrapper,如下:Java代码 package com.gdcn.bpaf.security.yale; import java.util....

云县19185481485: Spring中关于@ModelAttribute用于注解请求方法参数的问题 -
点刚摄护: @ModelAttribute可以用于注解方法和参数.1、注解Controller中的方法时,返回的参数是一个属性值,@ModelAttribute注解的方法在Controller中每个URL处理方法调用之前,都会按照先后顺序执行.2、注解Controller方法的参数,用于从...

云县19185481485: java架构师主要是干什么的? -
点刚摄护: 想成为java架构师,首先你自身得是一个高级java攻城狮,会使用各种框架并且很熟练,且知晓框架实现的原理. 比如,你要知道,jvm虚拟机原理、调优;懂得jvm能让你写出的代码性能更优化;还有池技术:什么对象池、连接池、线程池等等...

云县19185481485: 为什么android httpURLConnection get得到的不是页面源码 -
点刚摄护: 建议使用httpClient框架来发起get请求,以下是示例代码:1. GET 方式传递参数//先将参数放入List,再对参数进行URL编码List<BasicNameValuePair...

云县19185481485: 未将对象引用设置到对象的实例. 说明: 执行当前 Web 请求期间,出现未处理的异常.请检查堆栈跟踪信息, -
点刚摄护: 这个错误一般是你没有判断为空的情况出现的.比如当前页面需要一个参数,但是你没有传就会出现这种错误提示....

云县19185481485: jsp中如何判断request里有没有某个参数,比如 -
点刚摄护: 判断空就可以了,不要轻易使用toString()操作,这样容易报错,使用 if(request.getParameter("sss")!=null&&) String ss = request.getParameter("sss").toString(); else //没有这个参数的处理

云县19185481485: java中加签是什么意思 -
点刚摄护: 常见的http请求交互过程中,请求参数通过url或者request body等形式传输.但是由于http请求的开放性,使得请求参数很容易被拦截篡改.因此,需要对请求参数进行加签,然后在请求接受方对请求参数进行验签,确保两个签名是一样的,验签通过之后请求处理方就可以进行业务逻辑处理了.展开全部 但是,加签和验签只能解决请求传输过程中参数篡改的问题,并不能解决敏感参数传输的安全性问题.

云县19185481485: jQuery的Ajax请求参数到一个URL包含下划线问题,怎么解决 -
点刚摄护: 使用缓存会在请求中加上下划线的参数,jQuery默认开启缓存 设置cache为 false将在 HEAD和GET请求中正常工作.它的工作原理是在GET请求参数中附加"_={timestamp}"(译者注:时间戳).该参数不是其他请求所必须的,除了在IE8中,当一个POST请求一个已经用GET请求过的URL.

本站内容来自于网友发表,不代表本站立场,仅表示其个人看法,不对其真实性、正确性、有效性作任何的担保
相关事宜请发邮件给我们
© 星空见康网