总结基于 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
}))