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/

相关推荐
DemonAvenger6 小时前
深入剖析 sync.Once:实现原理、应用场景与实战经验
分布式·架构·go
一个热爱生活的普通人1 天前
Go语言中 Mutex 的实现原理
后端·go
孔令飞1 天前
关于 LLMOPS 的一些粗浅思考
人工智能·云原生·go
小戴同学1 天前
实时系统降低延时的利器
后端·性能优化·go
Golang菜鸟2 天前
golang中的组合多态
后端·go
Serverless社区2 天前
函数计算支持热门 MCP Server 一键部署
go
Wo3Shi4七2 天前
二叉树数组表示
数据结构·后端·go
网络研究院2 天前
您需要了解的有关 Go、Rust 和 Zig 的信息
开发语言·rust·go·功能·发展·zig
27669582922 天前
拼多多 anti-token unidbg 分析
java·python·go·拼多多·pdd·pxx·anti-token
程序员爱钓鱼3 天前
Go 语言邮件发送完全指南:轻松实现邮件通知功能
后端·go·排序算法