错误处理

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

{
    "code": 0,
    "msg": "successful",
    "data": {
        ....
    }
}

业务处理异常

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

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

用户名不存在

自定义错误

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

首先在ommon/errorx中添加一个baseerror.go文件,并填入代码

$ common/errorx
$ mkdir -p common/errorx && cd common/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,
    }
}

自定义成功返参

package response

import (
    "net/http"

    "github.com/zeromicro/go-zero/rest/httpx"
)

type Body struct {
    Status bool        `json:"status"`
    Code   int         `json:"code"`
    Msg    string      `json:"msg"`
    Data   interface{} `json:"data,omitempty"`
}

func Response(w http.ResponseWriter, resp interface{}, err error) {
    var body Body
    if err != nil {
        body.Code = -1
        body.Msg = err.Error()
    } else {
        body.Code = 200
        body.Msg = "success"
        body.Data = resp
    }
    httpx.OkJson(w, body)
}
func Ok(w http.ResponseWriter, resp interface{}, msg string) {
    var body Body
    body.Code = 200
    body.Msg = "OK"
    if len(msg) > 0 {
        body.Msg = msg
    }
    body.Data = resp
    httpx.OkJson(w, body)
}

开启自定义错误

开启自定义错误的代码建议写在模板里goctlTpl/1.4.2/api/main.tpl,就可以自动生成了

如果本地没有~/.goctl/${goctl版本号}/api/handler.tpl文件,可以通过模板初始化命令goctl template init进行初始化

$ vim service/user/api/user.go

代码

package main

import (
    "flag"
    "fmt"
    "github.com/zeromicro/go-zero/core/conf"
    "github.com/zeromicro/go-zero/rest"
    "github.com/zeromicro/go-zero/rest/httpx"
    "go-zero-demo/common/errorx"
    "go-zero-demo/user-api/internal/config"
    "go-zero-demo/user-api/internal/handler"
    "go-zero-demo/user-api/internal/svc"
    "net/http"
)

var configFile = flag.String("f", "etc/user-api.yaml", "the config file")

func main() {
    //读取控制台启动命令时带的参数 例 下面命令里-f 带的参数
    //go run user.go -f etc/greet-api.yaml
    flag.Parse()

    var c config.Config
    conf.MustLoad(*configFile, &c) //yaml配置初使化到config里

    ctx := svc.NewServiceContext(c)          //初使化ServiceContext配置文件
    server := rest.MustNewServer(c.RestConf) //启一个server
    defer server.Stop()                      //退出时,服务停时停止服务

    //注册路由
    handler.RegisterHandlers(server, ctx)

    // 自定义错误 start
    httpx.SetErrorHandlerCtx(func(ctx context.Context, err error) (int, interface{}) {
        //处理errors.New生成的错误,支持返回json格式, 兼容go-zero入参校验不过的情况
        if fmt.Sprintf("%T", err) == "*errors.errorString" {
            err = errorx.NewDefaultError(err.Error())
        }
        switch e := err.(type) {
        case *errorx.CodeError:
            return http.StatusOK, e.Data()
        default:
            return http.StatusInternalServerError, nil
        }
    })

    // 自定义错误 end

    fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
    //启动服务
    server.Start()

}

Handhle

func BlackIpsListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var req types.BlackIpsListReq
        if err := httpx.Parse(r, &req); err != nil {
            httpx.ErrorCtx(r.Context(), w, err)
            return
        }

        l := black_ips.NewBlackIpsListLogic(r.Context(), svcCtx)
        resp, err := l.BlackIpsList(&req)
        //response.Response(w, resp, err) //②

        if err != nil {
            httpx.ErrorCtx(r.Context(), w, err)
        } else {
            response.Ok(w, resp, "")
            //httpx.OkJsonCtx(r.Context(), w, resp)
        }
    }
}

重启服务验证

$ 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":"用户名不存在"}

参考:
https://go-zero.dev/cn/docs/advance/error-handle

作者:海马  创建时间:2022-10-30 08:25
最后编辑:海马  更新时间:2024-05-10 15:32