gin解析json格式的數(shù)據(jù)出錯(cuò)的處理方案
寫的接口給測試測試,現(xiàn)在還沒有頁面,直接測試接口。使用
c.BindJSON(&req)
總是報(bào)錯(cuò),大致錯(cuò)誤信息如下:
err="invalid character '-' in numeric literal"
這是由于我的接口要求將參數(shù)按照json格式傳遞到后臺(tái),結(jié)果測試同事使用了form-data格式,所以才會(huì)有上面這個(gè)錯(cuò)誤。
=============補(bǔ)充2018-11-09 18:20:00=============
剛剛又出現(xiàn)了這個(gè)EOF的問題,前端確定已經(jīng)按照json格式傳參,但是還是有這個(gè)問題。
通過wireshark抓包發(fā)現(xiàn),前端給的Content-Length為0,說明沒有將參數(shù)傳入后臺(tái)。
后來前端核查代碼發(fā)現(xiàn),確實(shí)是沒有將參數(shù)傳入,只是定義了
補(bǔ)充:gin json 獲取_Gin框架系列 自定義錯(cuò)誤處理
概述
很多讀者在后臺(tái)向我要 Gin 框架實(shí)戰(zhàn)系列的 Demo 源碼,在這里再說明一下,源碼我都更新到 GitHub 上,地址:https://github.com/xinliangnote/Go
開始今天的文章,為什么要自定義錯(cuò)誤處理?默認(rèn)的錯(cuò)誤處理方式是什么?
那好,咱們就先說下默認(rèn)的錯(cuò)誤處理。
默認(rèn)的錯(cuò)誤處理是 errors.New("錯(cuò)誤信息"),這個(gè)信息通過 error 類型的返回值進(jìn)行返回。
舉個(gè)簡單的例子:
func hello(name string) (str string, err error) { if name == "" { err = errors.New("name 不能為空") return } str = fmt.Sprintf("hello: %s", name) return }
當(dāng)調(diào)用這個(gè)方法時(shí):
var name = "" str, err := hello(name) if err != nil { fmt.Println(err.Error()) return }
這就是默認(rèn)的錯(cuò)誤處理,下面還會(huì)用這個(gè)例子進(jìn)行說。
這個(gè)默認(rèn)的錯(cuò)誤處理,只是得到了一個(gè)錯(cuò)誤信息的字符串。
然而...
我還想得到發(fā)生錯(cuò)誤時(shí)的 時(shí)間、 文件名、 方法名、 行號 等信息。
我還想得到錯(cuò)誤時(shí)進(jìn)行告警,比如 短信告警、 郵件告警、 微信告警 等。
我還想調(diào)用的時(shí)候,不那么復(fù)雜,就和默認(rèn)錯(cuò)誤處理類似,比如:
alarm.WeChat("錯(cuò)誤信息") return
這樣,我們就得到了我們想要的信息( 時(shí)間、 文件名、 方法名、 行號),并通過 微信 的方式進(jìn)行告警通知我們。
同理, alarm.Email("錯(cuò)誤信息")、 alarm.Sms("錯(cuò)誤信息") 我們得到的信息是一樣的,只是告警方式不同而已。
還要保證,我們業(yè)務(wù)邏輯中,獲取錯(cuò)誤的時(shí)候,只獲取錯(cuò)誤信息即可。
上面這些想出來的,就是今天要實(shí)現(xiàn)的,自定義錯(cuò)誤處理,我們就實(shí)現(xiàn)之前,先說下 Go 的錯(cuò)誤處理。
錯(cuò)誤處理
package main import ( "errors" "fmt" ) func hello(name string) (str string, err error) { if name == "" { err = errors.New("name 不能為空") return } str = fmt.Sprintf("hello: %s", name) return } func main() { var name = "" fmt.Println("param:", name) str, err := hello(name) if err != nil { fmt.Println(err.Error()) return } fmt.Println(str) }
輸出:
param: Tom
hello: Tom
當(dāng) name = "" 時(shí),輸出:
param:
name 不能為空
建議每個(gè)函數(shù)都要有錯(cuò)誤處理,error 應(yīng)該為最后一個(gè)返回值。
咱們一起看下官方 errors.go
// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package errors implements functions to manipulate errors. package errors // New returns an error that formats as the given text. func New(text string) error { return &errorString{text} } // errorString is a trivial implementation of error. type errorString struct { s string } func (e *errorString) Error() string { return e.s }
上面的代碼,并不復(fù)雜,參照上面的,咱們進(jìn)行寫一個(gè)自定義錯(cuò)誤處理。
自定義錯(cuò)誤處理
咱們定義一個(gè) alarm.go,用于處理告警。
廢話不多說,直接看代碼。
package alarm import ( "encoding/json" "fmt" "ginDemo/common/function" "path/filepath" "runtime" "strings" ) type errorString struct { s string } type errorInfo struct { Time string `json:"time"` Alarm string `json:"alarm"` Message string `json:"message"` Filename string `json:"filename"` Line int `json:"line"` Funcname string `json:"funcname"` } func (e *errorString) Error() string { return e.s } func New (text string) error { alarm("INFO", text) return &errorString{text} } // 發(fā)郵件 func Email (text string) error { alarm("EMAIL", text) return &errorString{text} } // 發(fā)短信 func Sms (text string) error { alarm("SMS", text) return &errorString{text} } // 發(fā)微信 func WeChat (text string) error { alarm("WX", text) return &errorString{text} } // 告警方法 func alarm(level string, str string) { // 當(dāng)前時(shí)間 currentTime := function.GetTimeStr() // 定義 文件名、行號、方法名 fileName, line, functionName := "?", 0 , "?" pc, fileName, line, ok := runtime.Caller(2) if ok { functionName = runtime.FuncForPC(pc).Name() functionName = filepath.Ext(functionName) functionName = strings.TrimPrefix(functionName, ".") } var msg = errorInfo { Time : currentTime, Alarm : level, Message : str, Filename : fileName, Line : line, Funcname : functionName, } jsons, errs := json.Marshal(msg) if errs != nil { fmt.Println("json marshal error:", errs) } errorJsonInfo := string(jsons) fmt.Println(errorJsonInfo) if level == "EMAIL" { // 執(zhí)行發(fā)郵件 } else if level == "SMS" { // 執(zhí)行發(fā)短信 } else if level == "WX" { // 執(zhí)行發(fā)微信 } else if level == "INFO" { // 執(zhí)行記日志 } }
看下如何調(diào)用:
package v1 import ( "fmt" "ginDemo/common/alarm" "ginDemo/entity" "github.com/gin-gonic/gin" "net/http" ) func AddProduct(c *gin.Context) { // 獲取 Get 參數(shù) name := c.Query("name") var res = entity.Result{} str, err := hello(name) if err != 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) } func hello(name string) (str string, err error) { if name == "" { 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 }
未拋出錯(cuò)誤,不會(huì)輸出信息。
訪問:http://localhost:8080/v1/product/add
{ "code": -1, "msg": "name 不能為空", "data": null }
拋出了錯(cuò)誤,輸出信息如下:
{"time":"2019-07-23 22:19:17","alarm":"WX","message":"name 不能為空","filename":"絕對路徑/ginDemo/router/v1/product.go","line":33,"funcname":"hello"}
可能這會(huì)有同學(xué)說:“用上一篇分享的數(shù)據(jù)綁定和驗(yàn)證,將傳入的參數(shù)進(jìn)行 binding:"required" 也可以實(shí)現(xiàn)呀”。
我只能說:“同學(xué)呀,你不理解我的良苦用心,這只是個(gè)例子,大家可以在一些復(fù)雜的業(yè)務(wù)邏輯判斷場景中使用自定義錯(cuò)誤處理”。
到這里,報(bào)錯(cuò)時(shí)我們收到了 時(shí)間、 錯(cuò)誤信息、 文件名、 行號、 方法名 了。
調(diào)用起來,也比較簡單。
雖然標(biāo)記了告警方式,還是沒有進(jìn)行告警通知呀。
我想說,在這里存儲(chǔ)數(shù)據(jù)到隊(duì)列中,再執(zhí)行異步任務(wù)具體去消耗,這塊就不實(shí)現(xiàn)了,大家可以去完善。
讀取 文件名、 方法名、 行號 使用的是 runtime.Caller()。
我們還知道,Go 有 panic 和 recover,它們是干什么的呢,接下來咱們就說說。
panic 和 recover
當(dāng)程序不能繼續(xù)運(yùn)行的時(shí)候,才應(yīng)該使用 panic 拋出錯(cuò)誤。
當(dāng)程序發(fā)生 panic 后,在 defer(延遲函數(shù)) 內(nèi)部可以調(diào)用 recover 進(jìn)行控制,不過有個(gè)前提條件,只有在相同的 Go 協(xié)程中才可以。
panic 分兩個(gè),一種是有意拋出的,一種是無意的寫程序馬虎造成的,咱們一個(gè)個(gè)說。
有意拋出的 panic:
package main import ( "fmt" ) func main() { fmt.Println("-- 1 --") defer func() { if r := recover(); r != nil { fmt.Printf("panic: %s\n", r) } fmt.Println("-- 2 --") }() panic("i am panic") }
輸出:
-- 1 --
panic: i am panic
-- 2 --
無意拋出的 panic:
package main import ( "fmt" ) func main() { fmt.Println("-- 1 --") defer func() { if r := recover(); r != nil { fmt.Printf("panic: %s\n", r) } fmt.Println("-- 2 --") }() var slice = [] int {1, 2, 3, 4, 5} slice[6] = 6 }
輸出:
-- 1 --
panic: runtime error: index out of range
-- 2 --
上面的兩個(gè)我們都通過 recover 捕獲到了,那我們?nèi)绾卧?Gin 框架中使用呢?如果收到 panic 時(shí),也想進(jìn)行告警怎么實(shí)現(xiàn)呢?
既然想實(shí)現(xiàn)告警,先在 ararm.go 中定義一個(gè) Panic() 方法,當(dāng)項(xiàng)目發(fā)生 panic 異常時(shí),調(diào)用這個(gè)方法,這樣就實(shí)現(xiàn)告警了。
// Panic 異常 func Panic (text string) error { alarm("PANIC", text) return &errorString{text} }
那我們怎么捕獲到呢?
使用中間件進(jìn)行捕獲,寫一個(gè) recover 中間件。
package recover import ( "fmt" "ginDemo/common/alarm" "github.com/gin-gonic/gin" ) func Recover() gin.HandlerFunc { return func(c *gin.Context) { defer func() { if r := recover(); r != nil { alarm.Panic(fmt.Sprintf("%s", r)) } }() c.Next() } }
路由調(diào)用中間件:
r.Use(logger.LoggerToFile(), recover.Recover()) //Use 可以傳遞多個(gè)中間件。
驗(yàn)證下吧,咱們先拋出兩個(gè)異常,看看能否捕獲到?
還是修改 product.go 這個(gè)文件吧。
有意拋出 panic:
package v1 import ( "fmt" "ginDemo/entity" "github.com/gin-gonic/gin" "net/http" ) func AddProduct(c *gin.Context) { // 獲取 Get 參數(shù) name := c.Query("name") var res = entity.Result{} str, err := hello(name) if err != 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) } func hello(name string) (str string, err error) { if name == "" { // 有意拋出 panic panic("i am panic") return } str = fmt.Sprintf("hello: %s", name) return }
訪問:http://localhost:8080/v1/product/add
界面是空白的。
拋出了異常,輸出信息如下:
{"time":"2019-07-23 22:42:37","alarm":"PANIC","message":"i am panic","filename":"絕對路徑/ginDemo/middleware/recover/recover.go","line":13,"funcname":"1"}
很顯然,定位的文件名、方法名、行號不是我們想要的。
需要調(diào)整 runtime.Caller(2),這個(gè)代碼在 alarm.go的alarm 方法中。
將 2 調(diào)整成 4 ,看下輸出信息:
{"time":"2019-07-23 22:45:24","alarm":"PANIC","message":"i am panic","filename":"絕對路徑/ginDemo/router/v1/product.go","line":33,"funcname":"hello"}
這就對了。
無意拋出 panic:
// 上面代碼不變 func hello(name string) (str string, err error) { if name == "" { // 無意拋出 panic var slice = [] 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-23 22:50:06","alarm":"PANIC","message":"runtime error: index out of range","filename":"絕對路徑/runtime/panic.go","line":44,"funcname":"panicindex"}
很顯然,定位的文件名、方法名、行號也不是我們想要的。
將 4 調(diào)整成 5 ,看下輸出信息:
{"time":"2019-07-23 22:55:27","alarm":"PANIC","message":"runtime error: index out of range","filename":"絕對路徑/ginDemo/router/v1/product.go","line":34,"funcname":"hello"}
這就對了。
奇怪了,這是為什么?
在這里,有必要說下 runtime.Caller(skip) 了。
skip 指的調(diào)用的深度。
為 0 時(shí),打印當(dāng)前調(diào)用文件及行數(shù)。
為 1 時(shí),打印上級調(diào)用的文件及行數(shù)。
依次類推...
在這塊,調(diào)用的時(shí)候需要注意下,我現(xiàn)在還沒有好的解決方案。
我是將 skip(調(diào)用深度),當(dāng)一個(gè)參數(shù)傳遞進(jìn)去。
比如:
// 發(fā)微信 func WeChat (text string) error { alarm("WX", text, 2) return &errorString{text} } // Panic 異常 func Panic (text string) error { alarm("PANIC", text, 5) return &errorString{text} }
具體的代碼就不貼了。
但是,有意拋出 Panic 和 無意拋出 Panic 的調(diào)用深度又不同,怎么辦?
1、盡量將有意拋出的 Panic 改成拋出錯(cuò)誤的方式。
2、想其他辦法搞定它。
就到這吧。
里面涉及到的代碼,我會(huì)更新到 GitHub。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
go gin+token(JWT)驗(yàn)證實(shí)現(xiàn)登陸驗(yàn)證
本文主要介紹了go gin+token(JWT)驗(yàn)證實(shí)現(xiàn)登陸驗(yàn)證,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12golang?gorm學(xué)習(xí)之如何指定數(shù)據(jù)表
在sql中首先要指定是從哪張表中查詢,所以這篇文章小編就來帶大家一起看一下gorm是如何根據(jù)model來自動(dòng)解析表名的,感興趣的小伙伴可以了解下2023-08-08Go type關(guān)鍵字(類型定義與類型別名的使用差異)用法實(shí)例探究
這篇文章主要為大家介紹了Go type關(guān)鍵字(類型定義與類型別名的使用差異)用法實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01Go設(shè)置http請求超時(shí)的方法實(shí)現(xiàn)
這篇文章主要介紹了Go設(shè)置http請求超時(shí)的方法實(shí)現(xiàn),最近接手了一個(gè)老項(xiàng)目進(jìn)行維護(hù),發(fā)現(xiàn)其中有個(gè)關(guān)于 http 請求的方法設(shè)置的 timeout 沒有生效,很奇怪,一開始查看代碼并沒有發(fā)現(xiàn)什么可疑點(diǎn),后查看了源碼,打斷點(diǎn)調(diào)試才發(fā)現(xiàn)問題所在,這里簡單記錄復(fù)盤一下2024-08-08Go+Redis實(shí)現(xiàn)常見限流算法的示例代碼
限流是項(xiàng)目中經(jīng)常需要使用到的一種工具,一般用于限制用戶的請求的頻率,也可以避免瞬間流量過大導(dǎo)致系統(tǒng)崩潰,或者穩(wěn)定消息處理速率。這篇文章主要是使用Go+Redis實(shí)現(xiàn)常見的限流算法,需要的可以參考一下2023-04-04Go 語言中的 http.FileSystem詳細(xì)解析
在本文中,我們深入探討了 Go 語言中的 http.FileSystem 接口,并介紹了它的基本原理、使用方法以及實(shí)際應(yīng)用場景,感興趣的朋友跟隨小編一起看看吧2024-03-03