错误处理

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

业务错误响应格式

  • 业务处理正常

    1. {
    2. "code": 0,
    3. "msg": "successful",
    4. "data": {
    5. ....
    6. }
    7. }
  • 业务处理异常

    1. {
    2. "code": 10001,
    3. "msg": "参数错误"
    4. }

user api之login

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

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

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

自定义错误

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

    1. $ cd common
    2. $ mkdir errorx&&cd errorx
    3. $ vim baseerror.go
    1. package errorx
    2. const defaultCode = 1001
    3. type CodeError struct {
    4. Code int `json:"code"`
    5. Msg string `json:"msg"`
    6. }
    7. type CodeErrorResponse struct {
    8. Code int `json:"code"`
    9. Msg string `json:"msg"`
    10. }
    11. func NewCodeError(code int, msg string) error {
    12. return &CodeError{Code: code, Msg: msg}
    13. }
    14. func NewDefaultError(msg string) error {
    15. return NewCodeError(defaultCode, msg)
    16. }
    17. func (e *CodeError) Error() string {
    18. return e.Msg
    19. }
    20. func (e *CodeError) Data() *CodeErrorResponse {
    21. return &CodeErrorResponse{
    22. Code: e.Code,
    23. Msg: e.Msg,
    24. }
    25. }
  • 将登录逻辑中错误用CodeError自定义错误替换

    1. if len(strings.TrimSpace(req.Username)) == 0 || len(strings.TrimSpace(req.Password)) == 0 {
    2. return nil, errorx.NewDefaultError("参数错误")
    3. }
    4. userInfo, err := l.svcCtx.UserModel.FindOneByNumber(req.Username)
    5. switch err {
    6. case nil:
    7. case model.ErrNotFound:
    8. return nil, errorx.NewDefaultError("用户名不存在")
    9. default:
    10. return nil, err
    11. }
    12. if userInfo.Password != req.Password {
    13. return nil, errorx.NewDefaultError("用户密码不正确")
    14. }
    15. now := time.Now().Unix()
    16. accessExpire := l.svcCtx.Config.Auth.AccessExpire
    17. jwtToken, err := l.getJwtToken(l.svcCtx.Config.Auth.AccessSecret, now, l.svcCtx.Config.Auth.AccessExpire, userInfo.Id)
    18. if err != nil {
    19. return nil, err
    20. }
    21. return &types.LoginReply{
    22. Id: userInfo.Id,
    23. Name: userInfo.Name,
    24. Gender: userInfo.Gender,
    25. AccessToken: jwtToken,
    26. AccessExpire: now + accessExpire,
    27. RefreshAfter: now + accessExpire/2,
    28. }, nil
  • 开启自定义错误

    1. $ vim service/user/cmd/api/user.go
    1. func main() {
    2. flag.Parse()
    3. var c config.Config
    4. conf.MustLoad(*configFile, &c)
    5. ctx := svc.NewServiceContext(c)
    6. server := rest.MustNewServer(c.RestConf)
    7. defer server.Stop()
    8. handler.RegisterHandlers(server, ctx)
    9. // 自定义错误
    10. httpx.SetErrorHandler(func(err error) (int, interface{}) {
    11. switch e := err.(type) {
    12. case *errorx.CodeError:
    13. return http.StatusOK, e.Data()
    14. default:
    15. return http.StatusInternalServerError, nil
    16. }
    17. })
    18. fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
    19. server.Start()
    20. }
  • 重启服务验证

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