Go - 全局异常捕获以及在 Gin 中的应用

在go开发过程中最常遇见的便是各种err!=nil错误判断,特别是java习惯了使用异常类处理,更会觉得go当中异常错误判断十分麻烦,因此本文中使用全局异常捕获结合自定义异常即可大大减少err判断,实现优雅的业务逻辑

一、全局异常捕获

通过 deferrecorder 实现异常捕获

go 复制代码
func main() {
	// 需要定义在逻辑之前
	defer func() {
		if err := recover(); err != nil {
			// 进行异常处理
			fmt.Println("happen error")
		}
	}()
	num1 := 1
	num2 := 0
	fmt.Println("cal before")
	res := num1 / num2
	fmt.Println("cal after")
	fmt.Println("cal res=", res)
}

执行结果如下

shell 复制代码
# go run main.go
cal before
happen error

二、如何在gin中使用

1.编写异常捕获中间件

go 复制代码
func ExceptionMiddleware(c *gin.Context) {
	defer func() {
		if err := recover(); err != nil {
      // 简单返回友好提示,具体可自定义发生错误后处理逻辑
			c.JSON(500, gin.H{"msg": "服务器发生错误xxxx"})
			c.Abort()
		}
	}()
	c.Next()
}

2.使用中间件

go 复制代码
func main(){
  router:=gin.Default()
  // 需要保证在异常代码发生之前加载才能拦截
  router.use(ExceptionMiddleware)

  router.GET("/hello",func(c *gin.Context){
    // 故意发生错误
    num1 := 1
    num2 := 0
    var res = num1 / num2
  })
}

3.验证

访问/hello地址将会得到如下结果

json 复制代码
{"msg": "服务器发生错误xxxx"}

三、优化异常捕获

异常捕获将会拦截所有的错误,但是很显然有些可控错误可以简单返回错误提示,有些系统错误又希望服务器记录并做不同逻辑处理,所以本节仍然以gin为例,讲述如何在异常捕获之后做不同逻辑

1.模拟简单的业务逻辑

go 复制代码
// 例如在某登录接口中
func Login(c *gin.Context){
    user,err:=GetUser(username,password)
    if err!=nil {
      c.JSON(500, gin.H{"msg": "用户未注册"})
    }
    c.JSON(200,user)
}

// 模拟service层查库
func GetUserById(username string,password string) user,error{
  user,err := db.xxx
  if err!=nil {
      return nil,err
  }
  return err,nil
}

2.自定义错误结构

go 复制代码
type WrapError struct {
	Code    int    `json:"code"`
	Msg string `json:"msg"`
}
func (err WrapError) Error() string {
	return err.Msg
}

3.修改异常捕获中间件

go 复制代码
func ExceptionMiddleware(c *gin.Context) {
	defer func() {
		if err := recover(); err != nil {
      // 修改错误信息解析
			c.JSON(500, gin.H{"msg": errorToString(err)})
			c.Abort()
		}
	}()
	c.Next()
}
func errorToString(err interface{}) string {
	switch v := err.(type) {
	case WrapError:
    // 符合预期的错误,可以直接返回给客户端
		return v.Msg
	case error:
		// 一律返回服务器错误,避免返回堆栈错误给客户端,实际还可以针对系统错误做其他处理
		debug.PrintStack()
		log.Printf("panic: %v\n", v.Error())
		return "服务器发生错误"
	default:
    // 同上
		return err.(string)
	}
}

4.修改业务逻辑

可以看到控制层已经没用err判断了,当然由于模拟逻辑太简单,没有很直观突出优化效果,实际上在项目中运用会大大减少代码量,且让逻辑看起来更又可读性,让控制层只有方法调用,更多的将业务逻辑放到service中,在service中有逻辑问题可以直接使用 panic 抛出,异常捕获便会封装好错误展示给客户端

go 复制代码
// 例如在某登录接口中
func Login(c *gin.Context){
    user := GetUser(username,password)
    c.JSON(200,user)
}

// 模拟service层查库
func GetUserById(username string,password string) user,error{
  // .....
  user,err := db.xxx
  if err!=nil {
      panic(WrapError({Msg:"账号或密码错误"}))
  }
  return err,nil
}

5.最后

最后,并不是说一定得做全局异常捕获处理,也并不代表这种方法就一定比用if判断err更好,可能更多的是以开发者的编程习惯去决定是否在项目中使用全局捕获。

博客内容遵循:署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议 本文永久链接是:seepine.com/go/error/

相关推荐
27669582921 天前
阿里1688 阿里滑块 231滑块 x5sec分析
java·python·go·验证码·1688·阿里滑块·231滑块
Moment2 天前
在 NodeJs 中如何通过子进程与 Golang 进行 IPC 通信 🙄🙄🙄
前端·后端·go
唐僧洗头爱飘柔95273 天前
(Go基础)变量与常量?字面量与变量的较量!
开发语言·后端·golang·go·go语言初上手
黑心萝卜三条杠3 天前
【Go语言】深入理解Go语言:并发、内存管理和垃圾回收
google·程序员·go
不喝水的鱼儿3 天前
【LuatOS】基于WebSocket的同步请求框架
网络·websocket·网络协议·go·luatos·lua5.4
微刻时光3 天前
程序员开发速查表
java·开发语言·python·docker·go·php·编程语言
lidenger4 天前
服务认证-来者何人
后端·go
幼儿园老大*4 天前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
童先生4 天前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
幼儿园老大*5 天前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go