gin解析json格式的数据出错的处理方案
写的接口给测试测试,现在还没有页面,直接测试接口。使用
c.BindJSON(&req)
总是报错,大致错误信息如下:
err="invalidcharacter'-'innumericliteral"
这是由于我的接口要求将参数按照json格式传递到后台,结果测试同事使用了form-data格式,所以才会有上面这个错误。
=============补充2018-11-0918:20:00=============
刚刚又出现了这个EOF的问题,前端确定已经按照json格式传参,但是还是有这个问题。
通过wireshark抓包发现,前端给的Content-Length为0,说明没有将参数传入后台。
后来前端核查代码发现,确实是没有将参数传入,只是定义了
补充:ginjson获取_Gin框架系列自定义错误处理
概述
很多读者在后台向我要Gin框架实战系列的Demo源码,在这里再说明一下,源码我都更新到GitHub上,地址:https://github.com/xinliangnote/Go
开始今天的文章,为什么要自定义错误处理?默认的错误处理方式是什么?
那好,咱们就先说下默认的错误处理。
默认的错误处理是errors.New("错误信息"),这个信息通过error类型的返回值进行返回。
举个简单的例子:
funchello(namestring)(strstring,errerror){
ifname==""{
err=errors.New("name不能为空")
return
}
str=fmt.Sprintf("hello:%s",name)
return
}
当调用这个方法时:
varname=""
str,err:=hello(name)
iferr!=nil{
fmt.Println(err.Error())
return
}
这就是默认的错误处理,下面还会用这个例子进行说。
这个默认的错误处理,只是得到了一个错误信息的字符串。
然而...
我还想得到发生错误时的时间、文件名、方法名、行号等信息。
我还想得到错误时进行告警,比如短信告警、邮件告警、微信告警等。
我还想调用的时候,不那么复杂,就和默认错误处理类似,比如:
alarm.WeChat("错误信息")
return
这样,我们就得到了我们想要的信息(时间、文件名、方法名、行号),并通过微信的方式进行告警通知我们。
同理,alarm.Email("错误信息")、alarm.Sms("错误信息")我们得到的信息是一样的,只是告警方式不同而已。
还要保证,我们业务逻辑中,获取错误的时候,只获取错误信息即可。
上面这些想出来的,就是今天要实现的,自定义错误处理,我们就实现之前,先说下Go的错误处理。
错误处理
packagemain
import(
"errors"
"fmt"
)
funchello(namestring)(strstring,errerror){
ifname==""{
err=errors.New("name不能为空")
return
}
str=fmt.Sprintf("hello:%s",name)
return
}
funcmain(){
varname=""
fmt.Println("param:",name)
str,err:=hello(name)
iferr!=nil{
fmt.Println(err.Error())
return
}
fmt.Println(str)
}
输出:
param:Tom
hello:Tom
当name=""时,输出:
param:
name不能为空
建议每个函数都要有错误处理,error应该为最后一个返回值。
咱们一起看下官方errors.go
//Copyright2011TheGoAuthors.Allrightsreserved.
//UseofthissourcecodeisgovernedbyaBSD-style
//licensethatcanbefoundintheLICENSEfile.
//Packageerrorsimplementsfunctionstomanipulateerrors.
packageerrors
//Newreturnsanerrorthatformatsasthegiventext.
funcNew(textstring)error{
return&errorString{text}
}
//errorStringisatrivialimplementationoferror.
typeerrorStringstruct{
sstring
}
func(e*errorString)Error()string{
returne.s
}
上面的代码,并不复杂,参照上面的,咱们进行写一个自定义错误处理。
自定义错误处理
咱们定义一个alarm.go,用于处理告警。
废话不多说,直接看代码。
packagealarm
import(
"encoding/json"
"fmt"
"ginDemo/common/function"
"path/filepath"
"runtime"
"strings"
)
typeerrorStringstruct{
sstring
}
typeerrorInfostruct{
Timestring`json:"time"`
Alarmstring`json:"alarm"`
Messagestring`json:"message"`
Filenamestring`json:"filename"`
Lineint`json:"line"`
Funcnamestring`json:"funcname"`
}
func(e*errorString)Error()string{
returne.s
}
funcNew(textstring)error{
alarm("INFO",text)
return&errorString{text}
}
//发邮件
funcEmail(textstring)error{
alarm("EMAIL",text)
return&errorString{text}
}
//发短信
funcSms(textstring)error{
alarm("SMS",text)
return&errorString{text}
}
//发微信
funcWeChat(textstring)error{
alarm("WX",text)
return&errorString{text}
}
//告警方法
funcalarm(levelstring,strstring){
//当前时间
currentTime:=function.GetTimeStr()
//定义文件名、行号、方法名
fileName,line,functionName:="?",0,"?"
pc,fileName,line,ok:=runtime.Caller(2)
ifok{
functionName=runtime.FuncForPC(pc).Name()
functionName=filepath.Ext(functionName)
functionName=strings.TrimPrefix(functionName,".")
}
varmsg=errorInfo{
Time:currentTime,
Alarm:level,
Message:str,
Filename:fileName,
Line:line,
Funcname:functionName,
}
jsons,errs:=json.Marshal(msg)
iferrs!=nil{
fmt.Println("jsonmarshalerror:",errs)
}
errorJsonInfo:=string(jsons)
fmt.Println(errorJsonInfo)
iflevel=="EMAIL"{
//执行发邮件
}elseiflevel=="SMS"{
//执行发短信
}elseiflevel=="WX"{
//执行发微信
}elseiflevel=="INFO"{
//执行记日志
}
}
看下如何调用:
packagev1
import(
"fmt"
"ginDemo/common/alarm"
"ginDemo/entity"
"github.com/gin-gonic/gin"
"net/http"
)
funcAddProduct(c*gin.Context){
//获取Get参数
name:=c.Query("name")
varres=entity.Result{}
str,err:=hello(name)
iferr!=nil{
res.SetCode(entity.CODE_ERROR)
res.SetMessage(err.Error())
c.JSON(http.StatusOK,res)
c.Abort()
return
}
res.SetCode(entity.CODE_SUCCESS)
res.SetMessage(str)
c.JSON(http.StatusOK,res)
}
funchello(namestring)(strstring,errerror){
ifname==""{
err=alarm.WeChat("name不能为空")
return
}
str=fmt.Sprintf("hello:%s",name)
return
}
访问:http://localhost:8080/v1/product/add?name=a
{
"code":1,
"msg":"hello:a",
"data":null
}
未抛出错误,不会输出信息。
访问:http://localhost:8080/v1/product/add
{
"code":-1,
"msg":"name不能为空",
"data":null
}
抛出了错误,输出信息如下:
{"time":"2019-07-2322:19:17","alarm":"WX","message":"name不能为空","filename":"绝对路径/ginDemo/router/v1/product.go","line":33,"funcname":"hello"}
可能这会有同学说:“用上一篇分享的数据绑定和验证,将传入的参数进行binding:"required"也可以实现呀”。
我只能说:“同学呀,你不理解我的良苦用心,这只是个例子,大家可以在一些复杂的业务逻辑判断场景中使用自定义错误处理”。
到这里,报错时我们收到了时间、错误信息、文件名、行号、方法名了。
调用起来,也比较简单。
虽然标记了告警方式,还是没有进行告警通知呀。
我想说,在这里存储数据到队列中,再执行异步任务具体去消耗,这块就不实现了,大家可以去完善。
读取文件名、方法名、行号使用的是runtime.Caller()。
我们还知道,Go有panic和recover,它们是干什么的呢,接下来咱们就说说。
panic和recover
当程序不能继续运行的时候,才应该使用panic抛出错误。
当程序发生panic后,在defer(延迟函数)内部可以调用recover进行控制,不过有个前提条件,只有在相同的Go协程中才可以。
panic分两个,一种是有意抛出的,一种是无意的写程序马虎造成的,咱们一个个说。
有意抛出的panic:
packagemain
import(
"fmt"
)
funcmain(){
fmt.Println("--1--")
deferfunc(){
ifr:=recover();r!=nil{
fmt.Printf("panic:%s\n",r)
}
fmt.Println("--2--")
}()
panic("iampanic")
}
输出:
--1--
panic:iampanic
--2--
无意抛出的panic:
packagemain
import(
"fmt"
)
funcmain(){
fmt.Println("--1--")
deferfunc(){
ifr:=recover();r!=nil{
fmt.Printf("panic:%s\n",r)
}
fmt.Println("--2--")
}()
varslice=[]int{1,2,3,4,5}
slice[6]=6
}
输出:
--1--
panic:runtimeerror:indexoutofrange
--2--
上面的两个我们都通过recover捕获到了,那我们如何在Gin框架中使用呢?如果收到panic时,也想进行告警怎么实现呢?
既然想实现告警,先在ararm.go中定义一个Panic()方法,当项目发生panic异常时,调用这个方法,这样就实现告警了。
//Panic异常
funcPanic(textstring)error{
alarm("PANIC",text)
return&errorString{text}
}
那我们怎么捕获到呢?
使用中间件进行捕获,写一个recover中间件。
packagerecover
import(
"fmt"
"ginDemo/common/alarm"
"github.com/gin-gonic/gin"
)
funcRecover()gin.HandlerFunc{
returnfunc(c*gin.Context){
deferfunc(){
ifr:=recover();r!=nil{
alarm.Panic(fmt.Sprintf("%s",r))
}
}()
c.Next()
}
}
路由调用中间件:
r.Use(logger.LoggerToFile(),recover.Recover()) //Use可以传递多个中间件。
验证下吧,咱们先抛出两个异常,看看能否捕获到?
还是修改product.go这个文件吧。
有意抛出panic:
packagev1
import(
"fmt"
"ginDemo/entity"
"github.com/gin-gonic/gin"
"net/http"
)
funcAddProduct(c*gin.Context){
//获取Get参数
name:=c.Query("name")
varres=entity.Result{}
str,err:=hello(name)
iferr!=nil{
res.SetCode(entity.CODE_ERROR)
res.SetMessage(err.Error())
c.JSON(http.StatusOK,res)
c.Abort()
return
}
res.SetCode(entity.CODE_SUCCESS)
res.SetMessage(str)
c.JSON(http.StatusOK,res)
}
funchello(namestring)(strstring,errerror){
ifname==""{
//有意抛出panic
panic("iampanic")
return
}
str=fmt.Sprintf("hello:%s",name)
return
}
访问:http://localhost:8080/v1/product/add
界面是空白的。
抛出了异常,输出信息如下:
{"time":"2019-07-2322:42:37","alarm":"PANIC","message":"iampanic","filename":"绝对路径/ginDemo/middleware/recover/recover.go","line":13,"funcname":"1"}
很显然,定位的文件名、方法名、行号不是我们想要的。
需要调整runtime.Caller(2),这个代码在alarm.go的alarm方法中。
将2调整成4,看下输出信息:
{"time":"2019-07-2322:45:24","alarm":"PANIC","message":"iampanic","filename":"绝对路径/ginDemo/router/v1/product.go","line":33,"funcname":"hello"}
这就对了。
无意抛出panic:
//上面代码不变
funchello(namestring)(strstring,errerror){
ifname==""{
//无意抛出panic
varslice=[]int{1,2,3,4,5}
slice[6]=6
return
}
str=fmt.Sprintf("hello:%s",name)
return
}
访问:http://localhost:8080/v1/product/add
界面是空白的。
抛出了异常,输出信息如下:
{"time":"2019-07-2322:50:06","alarm":"PANIC","message":"runtimeerror:indexoutofrange","filename":"绝对路径/runtime/panic.go","line":44,"funcname":"panicindex"}
很显然,定位的文件名、方法名、行号也不是我们想要的。
将4调整成5,看下输出信息:
{"time":"2019-07-2322:55:27","alarm":"PANIC","message":"runtimeerror:indexoutofrange","filename":"绝对路径/ginDemo/router/v1/product.go","line":34,"funcname":"hello"}
这就对了。
奇怪了,这是为什么?
在这里,有必要说下runtime.Caller(skip)了。
skip指的调用的深度。
为0时,打印当前调用文件及行数。
为1时,打印上级调用的文件及行数。
依次类推...
在这块,调用的时候需要注意下,我现在还没有好的解决方案。
我是将skip(调用深度),当一个参数传递进去。
比如:
//发微信
funcWeChat(textstring)error{
alarm("WX",text,2)
return&errorString{text}
}
//Panic异常
funcPanic(textstring)error{
alarm("PANIC",text,5)
return&errorString{text}
}
具体的代码就不贴了。
但是,有意抛出Panic和无意抛出Panic的调用深度又不同,怎么办?
1、尽量将有意抛出的Panic改成抛出错误的方式。
2、想其他办法搞定它。
就到这吧。
里面涉及到的代码,我会更新到GitHub。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持毛票票。如有错误或未考虑完全的地方,望不吝赐教。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。