75.错误码设计、实现统一异常处理和封装统一返回结果

在日常工作中,无论是RPC调用还是HTTP调用,异或是业务逻辑的处理,业务异常是很常见的,通常我们会为每个异常情况定义一个唯一的error。同时当发生异常的时候,我们也需要把异常信息放入到接口的响应信息里面,方便页面上做提示。

定义方式大致如下,当然工作中更常见的是使用专门的protoc或者thrift文件定义,这样对接方都可以依据这个协议文件知道错误的含义,从而做出相应的处理。

//业务异常
package bizerr

const (
 // 定义可预见的异常
  UserNotFound = 10001
  PasswrodErr = 10002
)

var resultCodeText = map[int]string{
  UserNotFound: "用户不存在",
   PasswrodErr: "密码错误",
}

func Message(code int) (string, bool) {
  message, ok := resultCodeText[code]
  return message, ok
}

错误码这里有5

10101
错误级别,如服务级模块级,如用户模块具体的错误码,如用户名错误

工作中每个团队定义方式可能不同,但是大致思路是一致的,即使用分段式,每个分段的位数是固定的,不同的段数的数值代表不同的含义,如下:

  • 错误级别:服务级错误用1,普通错误用2,通常是用户的非法操作

  • 模块级错误码:2 位数进行表示,比如 01 为用户模块;02 为订单模块

  • 具体错误码:2 位数进行表示,比如 01 为手机号不合法;02 为密码输入错误

为了让这些错误信息以及正常情况的返回数据都有统一的结构来管理,我们需要先定义一个统一返回的数据结构体。

Gin框架作为举例的话,要想实现统一的异常处理,我们可以借助Gin提供的中间件功能去在返回数据之前,拦截到出现的错误,在这里重新包装成我们定义的统一结构体。

package middleware

import (
  "net/http"

  "github.com/gin-gonic/gin"
)

// Result 表示统一响应的JSON格式
type Result struct {
  Code    int         `json:"code"`    // 状态码
  Message string      `json:"message"` // 响应消息
  Data    interface{} `json:"data"`    // 响应数据
}

接下来我们要给 Result 提供几个常用的方法,比如出现异常时候需要调用的方法,正常情况下需要调用的方法。

func Fail(c *gin.Context, code int, message string) {
  c.JSON(code, Result{
    Code:    code,
    Message: message,
    Data:    nil,
  })
   c.Abort()
}
  //异常信息从定义好的bizerr里面获取
 func Fail(c *gin.Context, code int) {
  message, _ := bizerr.StatusText(code)
  c.JSON(code, Result{
    Code:    code,
    Message: message,
    Data:    nil,
  })
  c.Abort()
}

//ok  不需要返回数据 data
func Ok(c *gin.Context, code int) {
  c.JSON(code, Result{
    Code:    code,
    Message: message,
    Data:    nil,
  })
}

//接口执行正常 需要返回数据 data
func Ok(c *gin.Context, code int, message string,
 data interface{} ) {
  c.JSON(code, Result{
    Code:    code,
    Message: message,
    Data:    data,
  })
}

//接口执行正常 需要返回数据 data
func Ok(c *gin.Context, code int,
 data interface{} )
 {
  c.JSON(code, Result{
    Code:    code,
    Message: "ok",
    Data:    data,
  })
}

实现拦截返回结果的中间件

func GlobalErrorMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
   //先执行请求
    c.Next()

    // 发生了错误
    if len(c.Errors) > 0 {
    //获取最后一个error 返回
      err := c.Errors.Last()
      Fail(c, http.StatusInternalServerError, err.Error())
      return
    }
    
  }
}

使用中间件

func main() {
  r := gin.New()
  r.Use(middleware.GlobalErrorMiddleware())

  r.GET("/test", func(c *gin.Context) {
    m := map[string]interface{}{
      "lang": "go",
       
     } 
    data:=Result{
     Data: m,
    }
    // 中途如果有其他错误返回了,会执行中间件封装统一的错误结构给前端,否则执行这个OK方法返回错误结构给前端
    // 不管有没有出错,前端收到的数据结构是我们开头统一定义的Result结构
    middleawre.Ok(http.Status.OK,data)
  })
  
  r.Run(":8080")
}

由此,我们看到了Gin提供的中间件的威力,中间件可以帮助我们做很多中间的事情。

通过定义统一的返回结构,使得我们的所有接口都可以以相同的数据结构展示给需要调用接口的人。大大提高了代码的可读性和维护性。

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
统一处理返回结果时,我们可以使用一个自定义的类来封装返回结果,这个类中可以包含需要返回的数据以及一些额外的信息,比如状态码、错误信息等。在这个类中,我们可以使用 Java 中的 Optional 类型来表示可能为 null 的字段,这样就可以保留 null 字段了。 例如,在返回结果中可能包含一个 User 对象,但是这个对象有可能为 null。我们可以定义一个返回结果类 Result<T>,其中 T 代表返回的数据类型,代码如下: ```java public class Result<T> { private int code; private String message; private T data; public Result(int code, String message, T data) { this.code = code; this.message = message; this.data = data; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public Optional<T> getData() { return Optional.ofNullable(data); } public void setData(T data) { this.data = data; } } ``` 在这个类中,我们使用了 Optional<T> 类型来表示可能为 null 的字段 data,在调用方获取 data 时,可以通过调用 Optional 的 get() 方法来获取 data 的值,如果 data 为 null,则会抛出 NoSuchElementException 异常,因此需要在调用 get() 方法前先判断 Optional 是否为 null。 例如,如果需要返回一个 User 对象,但是这个对象可能为 null,可以这样使用 Result 类: ```java public Result<User> getUser(int userId) { User user = userService.getUser(userId); if (user != null) { return new Result<>(200, "success", user); } else { return new Result<>(404, "not found", null); } } ``` 在调用方获取 User 对象时,可以这样使用: ```java Result<User> result = getUser(1001); if (result.getData().isPresent()) { User user = result.getData().get(); // 处理 user 对象 } else { // 处理返回结果为空的情况 } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值