错误处理

错误的处理是一个服务必不可缺的环节。在平时的业务开发中,我们可以认为http状态码不为2xx系列的,都可以认为是http请求错误, 并伴随响应的错误信息,但这些错误信息都是以plain text形式返回的。除此之外,我在业务中还会定义一些业务性错误,常用做法都是通过 codemsg 两个字段来进行业务处理结果描述,并且希望能够以json响应体来进行响应。

业务错误响应格式#

  • 业务处理正常

    {
    "code": 0,
    "msg": "successful",
    "data": {
    ....
    }
    }
  • 业务处理异常

    {
    "code": 10001,
    "msg": "参数错误"
    }

user api之login#

在之前,我们在登录逻辑中处理用户名不存在时,直接返回来一个error。我们来登录并传递一个不存在的用户名看看效果。

curl -X POST \
http://127.0.0.1:8888/user/login \
-H 'content-type: application/json' \
-d '{
"username":"1",
"password":"123456"
}'
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Tue, 09 Feb 2021 06:38:42 GMT
Content-Length: 19
用户名不存在

接下来我们将其以json格式进行返回

自定义错误#

  • 首先在common中添加一个baseerror.go文件,并填入代码

    $ cd common
    $ mkdir errorx && cd errorx
    $ vim baseerror.go
    package errorx
    const defaultCode = 1001
    type CodeError struct {
    Code int `json:"code"`
    Msg string `json:"msg"`
    }
    type CodeErrorResponse struct {
    Code int `json:"code"`
    Msg string `json:"msg"`
    }
    func NewCodeError(code int, msg string) error {
    return &CodeError{Code: code, Msg: msg}
    }
    func NewDefaultError(msg string) error {
    return NewCodeError(defaultCode, msg)
    }
    func (e *CodeError) Error() string {
    return e.Msg
    }
    func (e *CodeError) Data() *CodeErrorResponse {
    return &CodeErrorResponse{
    Code: e.Code,
    Msg: e.Msg,
    }
    }
  • 将登录逻辑中错误用CodeError自定义错误替换

    if len(strings.TrimSpace(req.Username)) == 0 || len(strings.TrimSpace(req.Password)) == 0 {
    return nil, errorx.NewDefaultError("参数错误")
    }
    userInfo, err := l.svcCtx.UserModel.FindOneByNumber(req.Username)
    switch err {
    case nil:
    case model.ErrNotFound:
    return nil, errorx.NewDefaultError("用户名不存在")
    default:
    return nil, err
    }
    if userInfo.Password != req.Password {
    return nil, errorx.NewDefaultError("用户密码不正确")
    }
    now := time.Now().Unix()
    accessExpire := l.svcCtx.Config.Auth.AccessExpire
    jwtToken, err := l.getJwtToken(l.svcCtx.Config.Auth.AccessSecret, now, l.svcCtx.Config.Auth.AccessExpire, userInfo.Id)
    if err != nil {
    return nil, err
    }
    return &types.LoginReply{
    Id: userInfo.Id,
    Name: userInfo.Name,
    Gender: userInfo.Gender,
    AccessToken: jwtToken,
    AccessExpire: now + accessExpire,
    RefreshAfter: now + accessExpire/2,
    }, nil
  • 开启自定义错误

    $ vim service/user/api/user.go
    func main() {
    flag.Parse()
    var c config.Config
    conf.MustLoad(*configFile, &c)
    ctx := svc.NewServiceContext(c)
    server := rest.MustNewServer(c.RestConf)
    defer server.Stop()
    handler.RegisterHandlers(server, ctx)
    // 自定义错误
    httpx.SetErrorHandler(func(err error) (int, interface{}) {
    switch e := err.(type) {
    case *errorx.CodeError:
    return http.StatusOK, e.Data()
    default:
    return http.StatusInternalServerError, nil
    }
    })
    fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
    server.Start()
    }
  • 重启服务验证

    $ curl -i -X POST \
    http://127.0.0.1:8888/user/login \
    -H 'content-type: application/json' \
    -d '{
    "username":"1",
    "password":"123456"
    }'
    HTTP/1.1 200 OK
    Content-Type: application/json
    Date: Tue, 09 Feb 2021 06:47:29 GMT
    Content-Length: 40
    {"code":1001,"msg":"用户名不存在"}
最后更新 日期: