引言
这篇博客不是关于错误堆栈的实现的,反正我的项目太小用不到(NX的嘲讽)
在Gin框架中,我们往往会直接使用下面形式进行错误处理
1 2 3 4 5 6
| if err!=nil{ c.JSON(http.statusOK,gin.H{ "msg" : "ok", "error" : err }) }
|
然而,我们会发现这种错误返回的方式,没有固定的结构,并且书写的时候非常冗余
那么我们就需要有一种固定形式来返回错误,同时丰富错误
算了说白了就是把c.JSON封装一下,更加高级一点,下面看一下实现
实现
结构
1 2 3 4
| errs: - code.go - logic.go - response.go
|
code.go:
预定义字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package errs
var ( SUCCESS = newError(200, "Success") )
var ( INVALID_REQUEST = newError(40001, "无效的请求") NOTFOUND = newError(40002, "目标不存在") HAS_EXIST = newError(40003, "目标已存在") LOGIN_ERROR = newError(40004, "d登陆失败") UNTHORIZATION = newError(40005, "鉴权失败") )
var ( DB_LINK_ERROR = newError(50001, "连接数据库失败") DB_CRUD_ERROR = newError(50002, "数据库操作失败") DB_BASE_ERROR = newError(50003, "数据库内部错误") )
var ( SERVE_INTERNAL = newError(60001, "服务器内部故障") )
|
logic.go:
整体代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| package errs
import ( "errors" "fmt" )
type Error struct { Code int64 `json:"code"` Message string `json:"message"` Origin string `json:"origin"` }
func newError(code int64, msg string) *Error { return &Error{ Code: code, Message: msg, } }
func (e *Error) Error() string { return e.Message }
func (e *Error) Is(target error) bool { var t *Error ok := errors.As(target, &t) if !ok { return false } return e.Code == t.Code }
func (e *Error) WithOrigin(err error) *Error { return &Error{ Code: e.Code, Message: e.Message, Origin: fmt.Sprintf("%+v", err), } }
func (e *Error) WithTips(details ...string) *Error { return &Error{ Code: e.Code, Message: e.Message + " " + fmt.Sprintf("%v", details), } }
|
解析
返回结构
1 2 3 4 5
| type Error struct { Code int64 `json:"code"` Message string `json:"message"` Origin string `json:"origin"` }
|
绑定预定义字段的基础含义
1 2 3 4 5 6 7
| func newError(code int64, msg string) *Error { return &Error{ Code: code, Message: msg, } }
|
默认error与自定义Error类型转换 —- errors.As
1 2 3 4 5
| var t *Error ok := errors.As ( _ error, &t) if !ok{ .... }
|
丰富Error字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func (e *Error) WithOrigin(err error) *Error { return &Error{ Code: e.Code, Message: e.Message, Origin: fmt.Sprintf("%+v", err), } }
func (e *Error) WithTips(details ...string) *Error { return &Error{ Code: e.Code, Message: e.Message + " " + fmt.Sprintf("%v", details), } }
|
两种方法都能够使用,一种是添加error类型,一种是添加字符串
注意这两个是方法,需要与预定义字段相结合来使用。
输出错误字符串
1 2 3
| func (e *Error) Error() string { return e.Message }
|
类似于 error.Error()就是来输出错误字符串的
response.go:
Success() + Fail()
定义一个响应的结构体字段
1 2 3 4 5 6
| type responseBody struct { Code int64 `json:"code"` Msg string `json:"msg"` Origin string `json:"origin"` Data any `json:"data"` }
|
前面三个字段是来对应之前Error的基础字段的
后面一个Data字段就是用来丰富Error数据内容的
Success:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func Success(c *gin.Context, data ...any) { response := responseBody{ Code: SUCCESS.Code, Msg: SUCCESS.Message, Origin: SUCCESS.Origin, Data: data, } c.JSON(http.StatusOK, response) }
|
因为传入 data …data 意味着可以无限传入不同数据类型的数据,因此丰富性提高
Fail:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func Fail(c *gin.Context, err error) { var e *Error ok := errors.As(err, &e) if !ok { e = SERVE_INTERNAL.WithOrigin(err) }
var resp responseBody resp.Code = e.Code resp.Msg = e.Message resp.Origin = e.Origin
c.JSON(int(e.Code/100), resp) c.Abort() }
|
注意 e = SERVE_INTERNAL.WithOrigin(err) 这个用法
SERVE_INTERNAL 是Error类型的 WithOrigin是Error类型的方法
因此用这个方法来引出是哪方面的基础错误-“服务器故障”
同时还可以附加自己的错误 - “err”
运行
随便挑一端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| func Login(c *gin.Context) { name := c.PostForm("name") password := c.PostForm("password")
var v2 model.User if tx := db.DB.Where(" name = ? ", name).First(&v2); tx.Error != nil { log.SugarLogger.Error(tx.Error) errs.Fail(c, errs.DB_CRUD_ERROR.WithOrigin(tx.Error)) return }
if password != v2.Password { errs.Fail(c, errs.LOGIN_ERROR.WithTips("密码错误")) return }
token, err := jwt.NewToken(name) if err != nil { log.SugarLogger.Error(err) errs.Fail(c, errs.UNTHORIZATION.WithOrigin(err)) return }
errs.Success(c, v2, map[string]string{"token": token}) }
|
成功:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| { "code": 200, "msg": "Success", "origin": "", "data": [ { "ID": 1, "CreatedAt": "2024-01-27T13:14:54.198+08:00", "UpdatedAt": "2024-02-02T15:48:39.952+08:00", "DeletedAt": null, "name": "John", "gender": "male", "age": 25, "birthday": "1999-01-01T08:00:00+08:00", "telephone": "1234567890", "password": "thirdsecreword", "email": "john@example.com" }, { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJ1c2VyIjoiSm9obiIsImlzcyI6IkVjaGluIiwic3ViIjoiVG9tIiwiZXhwIjoxNzA2OTY0Njc3fQ.iZLzNf3IUpFMPr1kV54nw8uugAkRzlu0VoJTWvlZjps" } ] }
|
失败:
1 2 3 4 5 6
| { "code": 50002, "msg": "数据库操作失败", "origin": "record not found", "data": null }
|
总结
整体的实现就是把c.JSON包装了一下,使得错误都统一化为预定义字段,同时还可以返回一些个人想要返回的个性化数据(通过WithOrigin WithTips)
再次强调,这个跟错误堆栈没啥关系,狗屎NX误导我,前一段时间全在思考为啥这个方法能够实现错误堆栈,明明都无法向上抛,