gin中间件统一返回结果、处理错误

gin框架中使用中间件统一返回结果,简化handler逻辑

现状及问题

在golang开发中我们经常会使用gin作为web框架,gin一直以高性能和简单著称,并且有十分完善的官方文档。然而gin的错误处理却一直以来都被人吐槽, 正常情况下我们在handler层需要根据service层返回的结果给前端相应的响应内容,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()
	r.GET("/hello", helloHandler)
	r.GET("/world", worldHandler)
	r.Run()
}

func helloHandler(c *gin.Context) {
	resp, err := helloService.Hello()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": err.Error(),
			"data":    nil,
		})
		return
	}
	c.JSON(http.StatusInternalServerError, gin.H{
		"message": "ok",
		"data":    resp,
	})
}

func worldHandler(c *gin.Context) {
	resp, err := worldService.World()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"message": err.Error(),
			"data":    nil,
		})
		return
	}
	c.JSON(http.StatusInternalServerError, gin.H{
		"message": "ok",
		"data":    resp,
	})
}

在每个handler中重复判断service是否返回异常未免太过繁琐,接下来我们通过golang的函数编程能力,使用中间件进行处理

简单方案

使用中间件改变handler函数,在中间件中处理返回逻辑

定义返回结构体、异常处理函数以及中间件Wrapper

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package middleware

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// 返回结构体
type resp struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Data    any    `json:"data"`
}

func errResp(message string) resp {
	return resp{
		Code:    http.StatusInternalServerError,
		Message: message,
		Data:    nil,
	}
}

func okResp(data interface{}) resp {
	return resp{
		Code:    http.StatusOK,
		Message: "ok",
		Data:    data,
	}
}

// ExceptionHandlerFunc 异常处理函数
type ExceptionHandlerFunc func(c *gin.Context) (data any, err error)

// Wrapper 中间件
func Wrapper(handlerFunc ExceptionHandlerFunc) func(c *gin.Context) {
	return func(c *gin.Context) {
		data, err := handlerFunc(c)
		if err != nil {
			c.JSON(http.StatusOK,errResp(err.Error()))
			return
		}
		c.JSON(http.StatusOK, okResp(data))
	}
}

修改router方法注册以及handler函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()
	r.GET("/hello", middleware.Wrapper(helloHandler)) // 使用Wrapper中间件
	r.GET("/world", middleware.Wrapper(worldHandler)) // 使用Wrapper中间件
	r.Run()
}

// 将handler改写为ExceptionHandlerFunc类型
func helloHandler(c *gin.Context)(data any, err error) {
	// 省略参数获取、验证等流程
	resp, err := helloService.Hello() // 从service获取结果并且处理返回
	// 省略对resp的处理
	return resp, err
}

// 将handler改写为ExceptionHandlerFunc类型
func worldHandler(c *gin.Context)(data any, err error) {
	// 省略参数获取、验证等流程
	return worldService.World() // 直接返回service结果
}

从代码可以看出,使用该方法可以简化handler方法的逻辑,并且实现起来相对简单

复杂方案

自定义错误类型、错误处理函数并在中间件中统一处理

定义结构体,实现Error接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// api错误的结构体
type APIException struct {
    Code        int     `json:"-"`
    ErrorCode   int     `json:"error_code"`
    Msg         string  `json:"msg"`
    Request     string  `json:"request"`
}
// 实现接口
func (e *APIException) Error() string {
    return  e.Msg
}

func newAPIException(code int, errorCode int,msg string) *APIException {
    return &APIException{
        Code:code,
        ErrorCode:errorCode,
        Msg:msg,
    }
}

定义错误处理的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const (
    SERVER_ERROR    = 1000 // 系统错误
    NOT_FOUND       = 1001 // 401错误
    UNKNOWN_ERROR   = 1002 // 未知错误
    PARAMETER_ERROR = 1003 // 参数错误
    AUTH_ERROR      = 1004 // 错误
)
// 500 错误处理
func ServerError() *APIException {
    return newAPIException(http.StatusInternalServerError,SERVER_ERROR,http.StatusText(http.StatusInternalServerError))
}
// 404 错误
func NotFound() *APIException {
    return newAPIException(http.StatusNotFound,NOT_FOUND,http.StatusText(http.StatusNotFound))
}
// 未知错误
func UnknownError(message string) *APIException {
    return newAPIException(http.StatusForbidden,UNKNOWN_ERROR,message)
}
// 参数错误
func ParameterError(message string) *APIException {
    return newAPIException(http.StatusBadRequest,PARAMETER_ERROR,message)
}

定义中间件统一处理错误

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func wrapper(handler HandlerFunc) func(c *gin.Context) {
    return func(c *gin.Context) {
        var (
            err error
        )
        err = handler(c)
        if err != nil {
            var apiException *APIException
            if h,ok := err.(*APIException); ok {
                apiException = h
            }else if e, ok := err.(error); ok {
                if gin.Mode() == "debug" {
                    // 错误
                    apiException = UnknownError(e.Error())
                }else{
                    // 未知错误
                    apiException = UnknownError(e.Error())
                }
            }else{
                apiException = ServerError()
            }
            apiException.Request = c.Request.Method + " "+ c.Request.URL.String()
            c.JSON(apiException.Code,apiException)
            return
        }
    }
}

定义404错误处理函数

1
2
3
4
5
6
func HandleNotFound(c *gin.Context) {
    handleErr := NotFound()
    handleErr.Request = c.Request.Method + " " + c.Request.URL.String()
    c.JSON(handleErr.Code,handleErr)
    return
}

使用

1
2
3
4
5
6
7
func main() {
    r := gin.Default()
    r.NoMethod(HandleNotFound)
    r.NoRoute(HandleNotFound)
    r.GET("/hello", wrapper(world))
    r.Run()
}
本博客已稳定运行 小时 分钟
共发表 31 篇文章 · 总计 82.93 k 字
本站总访问量
Built with Hugo
主题 StackJimmy 设计