新人第一篇:Gin框架使用小结

1,649 阅读3分钟

总结基于 REST api 开发过程中的各种问题,即 trouble-shooting:

一、Gin 接收 POST 的 json 参数 首先,我们知道 Gin 接收 POST 的 json 参数可以通过 BindJSON/ShouldBindJSON, 但发现参数还是未获取到,原因:BindJSON 的对象属性首字母必须大写,另外 binding:"required" tag 本身就会校验空字符串了。

二、单元测试httpexpect库 发送带JSON参数的请求

首先需定义一个 map[string]interface{}, 通过服务 e 发送请求时带 WithJSON(上述类型的对象),OK。

具体代码如下:

func TestSomething(t *testing.T){
    // 加载自定义路由到服务器:service.Router()为用户自己的实现
    server := httptest.NewServer(service.Router())
    e = httpexpect.New(t, server.URL)
    users := map[string]interface{}{
		"name": "mike",
	}
    resp := e.PUT("/users").WithJSON(users).Expect()
    // 断言响应码 200
    resp.Status(http.StatusOK)
}

三、gin 并发请求测试 测试结果:gin 默认支持并发请求 可以看到:

11点55分17秒 发送了一个带 flag=1 参数的请求,flag=1 表示让该线程 sleep 10s,

11点55分18秒 发送了一个不带 flag 参数的请求,表示不 sleep,只打印一条数据。

发现:

11.55.18秒 第二个请求先返回结果,说明 gin 底层是并发处理的,否则会等待第一个请求 sleep 结束后才会处理第二个请求。

测试代码:

func Get(c *gin.Context){
    flag := c.Query("flag")
    log.Printf("time:%v, flag:%v", time.Now(), flag)
    if flag == "1" {
	    log.Println("sleep 10 second.")
	    time.Sleep(10 * time.Second)
}
    c.JSON(200, flag)
}

四、REST Api 统一错误处理

在用 Gin 的 c.JSON 处理错误时发现每次都要手写一遍错误封装,并且没有任何规范:

func(c *gin.Context) {
    c.JSON(400, gin.H{
        "message": "参数非法",
    })
    return
}

在 gin 底层定义了这个函数的类型为 HandlerFunc

type HandlerFunc func(c *gin.Context) 

由于 gin 的返回结果只能是 HandlerFunc,因此我们要定义一个返回 HandlerFunc 的函数,在自定义的函数中实现统一错误处理,用户代码则只需 return 错误而不用关心如何处理。

type HandlerFuncWithError func(c *gin.Context) error

我们知道:函数在Go中是一等公民,可以像对象那样作为参数传入另一个函数,有点类似于装饰器。

func wrapper(handler HandlerFuncWithError) func(c *gin.Context) {
    return func(c *gin.Context) {
        err := handler(c)
        // 在这里统一规范错误响应格式并返回
    }
}

这样将用户定义的 HandlerFunc 作为参数传入装饰器, 实现 wrapper 统一错误处理, wrapper 就是 gin 支持的视图函数, 最后我们需要定义统一错误响应格式和一些错误。

定义一些错误码:

const (
	ServerERROR    = 1000 // 系统错误
	NotFOUND       = 1001 // 401错误 
	UnknownERROR   = 1002 // 未知错误
	ParameterERROR = 1003 // 参数错误
	AuthERROR      = 1004 // 验证错误
)

错误响应格式:

type APIException struct {
	Code      int    `json:"-"`
	ErrorCode int    `json:"err_code"`
	Message   string `json:"message"`
}

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

// 如:参数错误
func ParameterError(message string) *APIException {
	return newAPIException(http.StatusBadRequest, ParameterERROR, message)
}

使用 wrapper 作为视图函数:

router.go

// 错误处理统一收敛到 wrapper 装饰器
func wrapper(handler HandlerFuncWithError) func(c *gin.Context) {
	return func(c *gin.Context) {
		err := handler(c)
		if err != nil {
			c.JSON(err.Code, err)
			return
		}
	}
}

router:

r := gin.Default()
r.GET("/users", wrapper(func(c *gin.Context) *APIException {
		c.JSON(200, gin.H{
            "name":"mike"
            })
		return nil
	}))