Gin框架处理前端请求的时候,使用ShouldBindXXX绑定参数数据到结构体上是一种比较常用的取数据手段,但在一些情况下,可能会出现问题 例如,现在有一个userupdate接口,用于更新用户的年龄和昵称,即接收两个字段:age(int)、nickname(string),并且这两个字段并不要求必须同时传递,可以两个都传,也可以只传其中一个,后端从请求中解析这两个参数,取到哪个字段就对哪个字段进行更新typeUserstruct{Ageintjson:ageNickNamestringjson:nickname}复制代码 两个字段都传递那还好说,但如果只传其中一个字段,并且后端用ShouldBindXXX来绑定数据到结构体,就可能会出现问题了funcHandlerUpdate(cgin。Context){varuserUseriferr:c。ShouldBind(user);err!nil{。。。}}复制代码 如果前端只传了一个nickname字段,没传age字段,那么user。Age的值就是零值,即0,ShouldBindXXX并不判断这个0到底是零值还是前端真的传了0 这个问题解决起来倒也简单,两个方法 一是将结构体内的字段改成指针类型typeUserstruct{Ageintjson:ageNickNamestringjson:nickname}复制代码 指针的零值nil,ShouldBindXXX之后,字段值为nil的自然就是没传值的 但将结构体所有的字段都定义为指针类型未免有些不符合习惯,并且操作指针也不方便,也更容易出错(例如空指针问题) 第二个是办法是借助map ShouldBindXXX有问题的话那我大不了不用了,直接将参数(GET)数据(POST)映射到map就行 但这样的话就会引出另外一个问题,ShouldBindXXX方法一个显著的好处是可以根据结构体里定义的tag规则来对字段进行校验,如果你直接读到map中就要自己实现字段校验逻辑了,字段少点还好,要是多了得写一大串的if。。。else或者是干脆要实现一个通用校验方法了,未免繁琐 所以想到用ShouldBindXXX来做校验,再借助map用于区分零值,即对请求传递的数据读了两次 以GET请求为例:funcHandlerUpdate(cgin。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和nickname属性的时候,操作的数据表是dbuser,而这个数据表中除了age、nickname两列外,还存在用于标识用户是否注销了的isdel列,那么按照如下更新方式也是会出问题的:db。Model(user)。Updates(allMap)复制代码 如果allMap中存在isdel属性,那么也会更新数据表中的isdel字段,并不是预期的结果,所以需要将allMap中不需要的属性去掉,可以复制出一份只包含所需更新属性的map,也可以直接删除掉allMap上额外的属性只保留所需的,这里以前一种为例allMap:make(map〔string〕interface{})realMap:make(map〔string〕interface{})ifv,ok:allMap〔age〕;ok{realMap〔age〕v}ifv,ok:allMap〔nickname〕;ok{realMap〔nickname〕v}db。Model(user)。Updates(realMap)复制代码 这里只有age、nickname两个字段所以还好,但如果所需更新的字段最多在5个以上就要写最多5个条件语句了,未免繁琐,可以借助reflect处理,无论存在多少个需要更新的字段,代码量都是一样的realMap:make(map〔string〕interface{})typ:reflect。TypeOf(user)。Elem()fori:0;ityp。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(cgin。Context,instanceinterface{},mmap〔string〕interface{})error{ifc。ContentType()!gin。MIMEJSON{returnerrors。New(contenttypemustbegin。MIMEJSON)}ifc。Request。Method!http。MethodGetc。Request。Method!http。MethodPost{returnerrors。New(methodmustbeGETorPOST)}allMap:map〔string〕interface{}{}ifc。Request。Methodhttp。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;ityp。NumField();i{tagName:typ。Field(i)。Tag。Get(json)ifv,isOK:allMap〔tagName〕;isOK{(m)〔tagName〕v}}returnnil}从get请求中获取query,并将query处理成map映射到allMap中funcgenGetMap(cgin。Context,instanceinterface{},allMapmap〔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(cgin。Context,instanceinterface{},allMapmap〔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,okbody。(〔〕byte);!ok{returnerrors。New(bodyisinvalid)}iflen(bodyByte)0{returnnil}iferr:json。Unmarshal(bodyByte,allMap);err!nil{returnerr}returnnil}复制代码 使用示例:typeUserstruct{Ageintjson:ageNickNamestringjson:nickname}r。Any(update,func(cgin。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})})复制代码 可以看到,因为存在更多的计算过程,所以处理请求零值的情况,会带来更高的资源消耗,所以应该尽可能避免这种情况的出现,相比于在后端额外处理,让客户端携带完整的所需参数才是更优解 作者:清夜 链接:https:juejin。cnpost7016514018487566343 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。